summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/dashboard/todos_controller_spec.rb2
-rw-r--r--spec/controllers/projects/group_links_controller_spec.rb6
-rw-r--r--spec/controllers/projects/pipelines_controller_spec.rb22
-rw-r--r--spec/controllers/projects/project_members_controller_spec.rb14
-rw-r--r--spec/controllers/projects/settings/members_controller_spec.rb14
-rw-r--r--spec/db/production/settings.rb5
-rw-r--r--spec/factories/ci/pipelines.rb8
-rw-r--r--spec/factories/ci/runners.rb4
-rw-r--r--spec/factories/projects.rb4
-rw-r--r--spec/features/admin/admin_groups_spec.rb113
-rw-r--r--spec/features/admin/admin_users_spec.rb173
-rw-r--r--spec/features/ci_lint_spec.rb9
-rw-r--r--spec/features/cycle_analytics_spec.rb1
-rw-r--r--spec/features/dashboard/active_tab_spec.rb46
-rw-r--r--spec/features/dashboard/archived_projects_spec.rb28
-rw-r--r--spec/features/dashboard/group_spec.rb20
-rw-r--r--spec/features/dashboard/help_spec.rb17
-rw-r--r--spec/features/expand_collapse_diffs_spec.rb29
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb91
-rw-r--r--spec/features/issues/filtered_search/dropdown_assignee_spec.rb166
-rw-r--r--spec/features/issues/filtered_search/dropdown_author_spec.rb154
-rw-r--r--spec/features/issues/filtered_search/dropdown_hint_spec.rb134
-rw-r--r--spec/features/issues/filtered_search/dropdown_label_spec.rb242
-rw-r--r--spec/features/issues/filtered_search/dropdown_milestone_spec.rb222
-rw-r--r--spec/features/issues/filtered_search/filter_issues_spec.rb759
-rw-r--r--spec/features/issues/filtered_search/search_bar_spec.rb88
-rw-r--r--spec/features/issues/markdown_toolbar_spec.rb37
-rw-r--r--spec/features/issues/reset_filters_spec.rb89
-rw-r--r--spec/features/issues_spec.rb10
-rw-r--r--spec/features/merge_requests/deleted_source_branch_spec.rb19
-rw-r--r--spec/features/merge_requests/filter_by_labels_spec.rb (renamed from spec/features/issues/filter_by_labels_spec.rb)26
-rw-r--r--spec/features/merge_requests/filter_merge_requests_spec.rb (renamed from spec/features/issues/filter_issues_spec.rb)143
-rw-r--r--spec/features/merge_requests/reset_filters_spec.rb96
-rw-r--r--spec/features/projects/group_links_spec.rb4
-rw-r--r--spec/features/projects/members/anonymous_user_sees_members_spec.rb4
-rw-r--r--spec/features/projects/members/group_links_spec.rb4
-rw-r--r--spec/features/projects/members/group_members_spec.rb8
-rw-r--r--spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb8
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb441
-rw-r--r--spec/features/projects/services/mattermost_slash_command_spec.rb85
-rw-r--r--spec/features/search_spec.rb8
-rw-r--r--spec/features/security/project/internal_access_spec.rb4
-rw-r--r--spec/features/security/project/private_access_spec.rb4
-rw-r--r--spec/features/security/project/public_access_spec.rb4
-rw-r--r--spec/features/snippets/create_snippet_spec.rb14
-rw-r--r--spec/fixtures/config/redis_config_with_env.yml2
-rw-r--r--spec/initializers/secret_token_spec.rb7
-rw-r--r--spec/javascripts/.eslintrc23
-rw-r--r--spec/javascripts/abuse_reports_spec.js.es64
-rw-r--r--spec/javascripts/activities_spec.js.es64
-rw-r--r--spec/javascripts/awards_handler_spec.js4
-rw-r--r--spec/javascripts/behaviors/autosize_spec.js2
-rw-r--r--spec/javascripts/behaviors/quick_submit_spec.js4
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js4
-rw-r--r--spec/javascripts/bootstrap_linked_tabs_spec.js.es64
-rw-r--r--spec/javascripts/build_spec.js.es64
-rw-r--r--spec/javascripts/dashboard_spec.js.es66
-rw-r--r--spec/javascripts/environments/environment_actions_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_external_url_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_item_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_rollback_spec.js.es64
-rw-r--r--spec/javascripts/environments/environment_stop_spec.js.es64
-rw-r--r--spec/javascripts/extensions/jquery_spec.js4
-rw-r--r--spec/javascripts/filtered_search/dropdown_utils_spec.js.es6107
-rw-r--r--spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es659
-rw-r--r--spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6104
-rw-r--r--spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6104
-rw-r--r--spec/javascripts/fixtures/projects.json446
-rw-r--r--spec/javascripts/fixtures/static_fixtures.rb31
-rw-r--r--spec/javascripts/fixtures/u2f.rb43
-rw-r--r--spec/javascripts/fixtures/u2f/authenticate.html.haml1
-rw-r--r--spec/javascripts/fixtures/u2f/register.html.haml2
-rw-r--r--spec/javascripts/gl_dropdown_spec.js.es67
-rw-r--r--spec/javascripts/gl_field_errors_spec.js.es64
-rw-r--r--spec/javascripts/header_spec.js6
-rw-r--r--spec/javascripts/issuable_spec.js.es66
-rw-r--r--spec/javascripts/issue_spec.js12
-rw-r--r--spec/javascripts/labels_issue_sidebar_spec.js.es64
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js.es624
-rw-r--r--spec/javascripts/lib/utils/text_utility_spec.js.es625
-rw-r--r--spec/javascripts/line_highlighter_spec.js4
-rw-r--r--spec/javascripts/merge_request_spec.js4
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js4
-rw-r--r--spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es64
-rw-r--r--spec/javascripts/new_branch_spec.js4
-rw-r--r--spec/javascripts/notes_spec.js6
-rw-r--r--spec/javascripts/pipelines_spec.js.es64
-rw-r--r--spec/javascripts/project_title_spec.js7
-rw-r--r--spec/javascripts/right_sidebar_spec.js7
-rw-r--r--spec/javascripts/search_autocomplete_spec.js10
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js4
-rw-r--r--spec/javascripts/signin_tabs_memoizer_spec.js.es66
-rw-r--r--spec/javascripts/smart_interval_spec.js.es62
-rw-r--r--spec/javascripts/spec_helper.js10
-rw-r--r--spec/javascripts/syntax_highlight_spec.js6
-rw-r--r--spec/javascripts/u2f/authenticate_spec.js4
-rw-r--r--spec/javascripts/u2f/register_spec.js4
-rw-r--r--spec/javascripts/vue_common_components/commit_spec.js.es66
-rw-r--r--spec/javascripts/vue_pagination/pagination_spec.js.es6168
-rw-r--r--spec/javascripts/zen_mode_spec.js4
-rw-r--r--spec/lib/api/helpers/pagination_spec.rb94
-rw-r--r--spec/lib/ci/ansi2html_spec.rb8
-rw-r--r--spec/lib/ci/gitlab_ci_yaml_processor_spec.rb13
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb4
-rw-r--r--spec/lib/gitlab/backup/manager_spec.rb114
-rw-r--r--spec/lib/gitlab/checks/change_access_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/entry/environment_spec.rb17
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml1
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb63
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb23
-rw-r--r--spec/lib/gitlab/ldap/person_spec.rb12
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb11
-rw-r--r--spec/lib/gitlab/redis_spec.rb16
-rw-r--r--spec/migrations/fill_authorized_projects_spec.rb18
-rw-r--r--spec/migrations/remove_dot_git_from_usernames.rb29
-rw-r--r--spec/migrations/remove_dot_git_from_usernames_spec.rb57
-rw-r--r--spec/models/build_spec.rb1338
-rw-r--r--spec/models/ci/build_spec.rb1278
-rw-r--r--spec/models/ci/pipeline_spec.rb42
-rw-r--r--spec/models/commit_status_spec.rb19
-rw-r--r--spec/models/environment_spec.rb17
-rw-r--r--spec/models/global_milestone_spec.rb86
-rw-r--r--spec/models/group_milestone_spec.rb32
-rw-r--r--spec/models/key_spec.rb9
-rw-r--r--spec/models/label_spec.rb2
-rw-r--r--spec/models/project_services/bamboo_service_spec.rb149
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb77
-rw-r--r--spec/models/project_services/drone_ci_service_spec.rb72
-rw-r--r--spec/models/project_services/teamcity_service_spec.rb126
-rw-r--r--spec/models/project_spec.rb41
-rw-r--r--spec/models/repository_spec.rb26
-rw-r--r--spec/requests/api/issues_spec.rb82
-rw-r--r--spec/requests/api/projects_spec.rb17
-rw-r--r--spec/requests/api/settings_spec.rb16
-rw-r--r--spec/requests/api/users_spec.rb17
-rw-r--r--spec/serializers/build_action_entity_spec.rb21
-rw-r--r--spec/serializers/build_artifact_entity_spec.rb22
-rw-r--r--spec/serializers/commit_entity_spec.rb6
-rw-r--r--spec/serializers/pipeline_entity_spec.rb138
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb101
-rw-r--r--spec/serializers/request_aware_entity_spec.rb22
-rw-r--r--spec/serializers/stage_entity_spec.rb51
-rw-r--r--spec/serializers/status_entity_spec.rb23
-rw-r--r--spec/services/projects/participants_service_spec.rb32
-rw-r--r--spec/services/users/refresh_authorized_projects_service_spec.rb17
-rw-r--r--spec/support/javascript_fixtures_helpers.rb21
-rw-r--r--spec/support/reactive_caching_helpers.rb30
-rw-r--r--spec/support/seed_helper.rb10
-rw-r--r--spec/support/stub_env.rb7
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb2
-rw-r--r--spec/views/projects/merge_requests/show.html.haml_spec.rb7
-rw-r--r--spec/views/shared/milestones/_issuables.html.haml.rb32
-rw-r--r--spec/workers/use_key_worker_spec.rb23
154 files changed, 6942 insertions, 2278 deletions
diff --git a/spec/controllers/dashboard/todos_controller_spec.rb b/spec/controllers/dashboard/todos_controller_spec.rb
index 288984cfba9..19fbc2f7748 100644
--- a/spec/controllers/dashboard/todos_controller_spec.rb
+++ b/spec/controllers/dashboard/todos_controller_spec.rb
@@ -12,7 +12,7 @@ describe Dashboard::TodosController do
end
context 'when using pagination' do
- let(:last_page) { user.todos.page().total_pages }
+ let(:last_page) { user.todos.page.total_pages }
let!(:issues) { create_list(:issue, 2, project: project, assignee: user) }
before do
diff --git a/spec/controllers/projects/group_links_controller_spec.rb b/spec/controllers/projects/group_links_controller_spec.rb
index b9d9117c928..17dc101b7ee 100644
--- a/spec/controllers/projects/group_links_controller_spec.rb
+++ b/spec/controllers/projects/group_links_controller_spec.rb
@@ -31,7 +31,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_group_links_path(project.namespace, project)
+ namespace_project_settings_members_path(project.namespace, project)
)
end
end
@@ -62,7 +62,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_group_links_path(project.namespace, project)
+ namespace_project_settings_members_path(project.namespace, project)
)
end
end
@@ -76,7 +76,7 @@ describe Projects::GroupLinksController do
it 'redirects to project group links page' do
expect(response).to redirect_to(
- namespace_project_group_links_path(project.namespace, project)
+ namespace_project_settings_members_path(project.namespace, project)
)
expect(flash[:alert]).to eq('Please select a group.')
end
diff --git a/spec/controllers/projects/pipelines_controller_spec.rb b/spec/controllers/projects/pipelines_controller_spec.rb
index 5fe7e6407cc..1ed2ee3ab4a 100644
--- a/spec/controllers/projects/pipelines_controller_spec.rb
+++ b/spec/controllers/projects/pipelines_controller_spec.rb
@@ -5,13 +5,33 @@ describe Projects::PipelinesController do
let(:user) { create(:user) }
let(:project) { create(:empty_project, :public) }
- let(:pipeline) { create(:ci_pipeline, project: project) }
before do
sign_in(user)
end
+ describe 'GET index.json' do
+ before do
+ create_list(:ci_empty_pipeline, 2, project: project)
+
+ get :index, namespace_id: project.namespace.path,
+ project_id: project.path,
+ format: :json
+ end
+
+ it 'returns JSON with serialized pipelines' do
+ expect(response).to have_http_status(:ok)
+
+ expect(json_response).to include('pipelines')
+ expect(json_response['pipelines'].count).to eq 2
+ expect(json_response['count']['all']).to eq 2
+ expect(json_response['count']['running_or_pending']).to eq 2
+ end
+ end
+
describe 'GET stages.json' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
context 'when accessing existing stage' do
before do
create(:ci_build, pipeline: pipeline, stage: 'build')
diff --git a/spec/controllers/projects/project_members_controller_spec.rb b/spec/controllers/projects/project_members_controller_spec.rb
index b52137fbe7e..442f81187dc 100644
--- a/spec/controllers/projects/project_members_controller_spec.rb
+++ b/spec/controllers/projects/project_members_controller_spec.rb
@@ -5,11 +5,11 @@ describe Projects::ProjectMembersController do
let(:project) { create(:empty_project, :public, :access_requestable) }
describe 'GET index' do
- it 'renders index with 200 status code' do
+ it 'should have the settings/members address with a 302 status code' do
get :index, namespace_id: project.namespace, project_id: project
- expect(response).to have_http_status(200)
- expect(response).to render_template(:index)
+ expect(response).to have_http_status(302)
+ expect(response.location).to include namespace_project_settings_members_path(project.namespace, project)
end
end
@@ -44,7 +44,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'Users were successfully added.'
- expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project))
+ expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
end
it 'adds no user to members' do
@@ -56,7 +56,7 @@ describe Projects::ProjectMembersController do
access_level: Gitlab::Access::GUEST
expect(response).to set_flash.to 'No users or groups specified.'
- expect(response).to redirect_to(namespace_project_project_members_path(project.namespace, project))
+ expect(response).to redirect_to(namespace_project_settings_members_path(project.namespace, project))
end
end
end
@@ -99,7 +99,7 @@ describe Projects::ProjectMembersController do
id: member
expect(response).to redirect_to(
- namespace_project_project_members_path(project.namespace, project)
+ namespace_project_settings_members_path(project.namespace, project)
)
expect(project.members).not_to include member
end
@@ -259,7 +259,7 @@ describe Projects::ProjectMembersController do
expect(project.team_members).to include member
expect(response).to set_flash.to 'Successfully imported'
expect(response).to redirect_to(
- namespace_project_project_members_path(project.namespace, project)
+ namespace_project_settings_members_path(project.namespace, project)
)
end
end
diff --git a/spec/controllers/projects/settings/members_controller_spec.rb b/spec/controllers/projects/settings/members_controller_spec.rb
new file mode 100644
index 00000000000..076d6cd9c6e
--- /dev/null
+++ b/spec/controllers/projects/settings/members_controller_spec.rb
@@ -0,0 +1,14 @@
+require('spec_helper')
+
+describe Projects::Settings::MembersController do
+ let(:project) { create(:empty_project, :public, :access_requestable) }
+
+ describe 'GET show' do
+ it 'renders show with 200 status code' do
+ get :show, namespace_id: project.namespace, project_id: project
+
+ expect(response).to have_http_status(200)
+ expect(response).to render_template(:show)
+ end
+ end
+end
diff --git a/spec/db/production/settings.rb b/spec/db/production/settings.rb
index a7c5283df94..007b35bbb77 100644
--- a/spec/db/production/settings.rb
+++ b/spec/db/production/settings.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
require 'rainbow/ext/string'
describe 'seed production settings', lib: true do
+ include StubENV
+
context 'GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN is set in the environment' do
before do
- allow(ENV).to receive(:[]).and_call_original
- allow(ENV).to receive(:[]).with('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN').and_return('013456789')
+ stub_env('GITLAB_SHARED_RUNNERS_REGISTRATION_TOKEN', '013456789')
end
it 'writes the token to the database' do
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 1735791f644..77404f46c92 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -31,6 +31,14 @@ FactoryGirl.define do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci.yml'))
end
end
+
+ # Populates pipeline with errors
+ #
+ pipeline.config_processor if evaluator.config
+ end
+
+ trait :invalid do
+ config(rspec: nil)
end
end
end
diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb
index e3b73e29987..ed4acca23f1 100644
--- a/spec/factories/ci/runners.rb
+++ b/spec/factories/ci/runners.rb
@@ -8,6 +8,10 @@ FactoryGirl.define do
is_shared false
active true
+ trait :online do
+ contacted_at Time.now
+ end
+
trait :shared do
is_shared true
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index f7fa834d7a2..1cdbe4fc9a5 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -24,6 +24,10 @@ FactoryGirl.define do
visibility_level Gitlab::VisibilityLevel::PRIVATE
end
+ trait :archived do
+ archived true
+ end
+
trait :access_requestable do
request_access_enabled true
end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index 9c19db6b420..a871e370ba2 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -1,15 +1,39 @@
require 'spec_helper'
feature 'Admin Groups', feature: true do
+ include Select2Helper
+
let(:internal) { Gitlab::VisibilityLevel::INTERNAL }
+ let(:user) { create :user }
+ let!(:group) { create :group }
+ let!(:current_user) { login_as :admin }
before do
- login_as(:admin)
-
stub_application_setting(default_group_visibility: internal)
end
+ describe 'list' do
+ it 'renders groups' do
+ visit admin_groups_path
+
+ expect(page).to have_content(group.name)
+ end
+ end
+
describe 'create a group' do
+ it 'creates new group' do
+ visit admin_groups_path
+
+ click_link "New Group"
+ fill_in 'group_path', with: 'gitlab'
+ fill_in 'group_description', with: 'Group description'
+ click_button "Create group"
+
+ expect(current_path).to eq admin_group_path(Group.find_by(path: 'gitlab'))
+ expect(page).to have_content('Group: gitlab')
+ expect(page).to have_content('Group description')
+ end
+
scenario 'shows the visibility level radio populated with the default value' do
visit new_admin_group_path
@@ -37,6 +61,91 @@ feature 'Admin Groups', feature: true do
end
end
+ describe 'add user into a group', js: true do
+ shared_context 'adds user into a group' do
+ it do
+ visit admin_group_path(group)
+
+ select2(user_selector, from: '#user_ids', multiple: true)
+ page.within '#new_project_member' do
+ select2(Gitlab::Access::REPORTER, from: '#access_level')
+ end
+ click_button "Add users to group"
+ page.within ".group-users-list" do
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('Reporter')
+ end
+ end
+ end
+
+ it_behaves_like 'adds user into a group' do
+ let(:user_selector) { user.id }
+ end
+
+ it_behaves_like 'adds user into a group' do
+ let(:user_selector) { user.email }
+ end
+ end
+
+ describe 'add admin himself to a group' do
+ before do
+ group.add_user(:user, Gitlab::Access::OWNER)
+ end
+
+ it 'adds admin a to a group as developer', js: true do
+ visit group_group_members_path(group)
+
+ page.within '.users-group-form' do
+ select2(current_user.id, from: '#user_ids', multiple: true)
+ select 'Developer', from: 'access_level'
+ end
+
+ click_button 'Add to group'
+
+ page.within '.content-list' do
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content('Developer')
+ end
+ end
+ end
+
+ describe 'admin remove himself from a group', js: true do
+ it 'removes admin from the group' do
+ group.add_user(current_user, Gitlab::Access::DEVELOPER)
+
+ visit group_group_members_path(group)
+
+ page.within '.content-list' do
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content('Developer')
+ end
+
+ find(:css, 'li', text: current_user.name).find(:css, 'a.btn-remove').click
+
+ visit group_group_members_path(group)
+
+ page.within '.content-list' do
+ expect(page).not_to have_content(current_user.name)
+ expect(page).not_to have_content('Developer')
+ end
+ end
+ end
+
+ describe 'shared projects' do
+ it 'renders shared project' do
+ empty_project = create(:empty_project)
+ empty_project.project_group_links.create!(
+ group_access: Gitlab::Access::MASTER,
+ group: group
+ )
+
+ visit admin_group_path(group)
+
+ expect(page).to have_content(empty_project.name_with_namespace)
+ expect(page).to have_content('Projects shared with')
+ end
+ end
+
def expect_selected_visibility(level)
selector = "#group_visibility_level_#{level}[checked=checked]"
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index 55ffc6761f8..a586f8d3184 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -1,9 +1,13 @@
require 'spec_helper'
-describe "Admin::Users", feature: true do
+describe "Admin::Users", feature: true do
include WaitForAjax
- before { login_as :admin }
+ let!(:user) do
+ create(:omniauth_user, provider: 'twitter', extern_uid: '123456')
+ end
+
+ let!(:current_user) { login_as :admin }
describe "GET /admin/users" do
before do
@@ -15,8 +19,10 @@ describe "Admin::Users", feature: true do
end
it "has users list" do
- expect(page).to have_content(@user.email)
- expect(page).to have_content(@user.name)
+ expect(page).to have_content(current_user.email)
+ expect(page).to have_content(current_user.name)
+ expect(page).to have_content(user.email)
+ expect(page).to have_content(user.name)
end
describe 'Two-factor Authentication filters' do
@@ -40,8 +46,6 @@ describe "Admin::Users", feature: true do
end
it 'counts users who have not enabled 2FA' do
- create(:user)
-
visit admin_users_path
page.within('.filter-two-factor-disabled small') do
@@ -50,8 +54,6 @@ describe "Admin::Users", feature: true do
end
it 'filters by users who have not enabled 2FA' do
- user = create(:user)
-
visit admin_users_path
click_link '2FA Disabled'
@@ -110,10 +112,10 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id" do
it "has user info" do
visit admin_users_path
- click_link @user.name
+ click_link user.name
- expect(page).to have_content(@user.email)
- expect(page).to have_content(@user.name)
+ expect(page).to have_content(user.email)
+ expect(page).to have_content(user.name)
end
describe 'Impersonation' do
@@ -126,7 +128,7 @@ describe "Admin::Users", feature: true do
end
it 'does not show impersonate button for admin itself' do
- visit admin_user_path(@user)
+ visit admin_user_path(current_user)
expect(page).not_to have_content('Impersonate')
end
@@ -158,7 +160,7 @@ describe "Admin::Users", feature: true do
it 'logs out of impersonated user back to original user' do
find(:css, 'li.impersonation a').click
- expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(@user.username)
+ expect(page.find(:css, '.header-user .profile-link')['data-user']).to eql(current_user.username)
end
it 'is redirected back to the impersonated users page in the admin after stopping' do
@@ -171,15 +173,15 @@ describe "Admin::Users", feature: true do
describe 'Two-factor Authentication status' do
it 'shows when enabled' do
- @user.update_attribute(:otp_required_for_login, true)
+ user.update_attribute(:otp_required_for_login, true)
- visit admin_user_path(@user)
+ visit admin_user_path(user)
expect_two_factor_status('Enabled')
end
it 'shows when disabled' do
- visit admin_user_path(@user)
+ visit admin_user_path(user)
expect_two_factor_status('Disabled')
end
@@ -194,9 +196,8 @@ describe "Admin::Users", feature: true do
describe "GET /admin/users/:id/edit" do
before do
- @simple_user = create(:user)
visit admin_users_path
- click_link "edit_user_#{@simple_user.id}"
+ click_link "edit_user_#{user.id}"
end
it "has user edit page" do
@@ -214,45 +215,58 @@ describe "Admin::Users", feature: true do
click_button "Save changes"
end
- it "shows page with new data" do
+ it "shows page with new data" do
expect(page).to have_content('bigbang@mail.com')
expect(page).to have_content('Big Bang')
end
it "changes user entry" do
- @simple_user.reload
- expect(@simple_user.name).to eq('Big Bang')
- expect(@simple_user.is_admin?).to be_truthy
- expect(@simple_user.password_expires_at).to be <= Time.now
+ user.reload
+ expect(user.name).to eq('Big Bang')
+ expect(user.is_admin?).to be_truthy
+ expect(user.password_expires_at).to be <= Time.now
+ end
+ end
+
+ describe 'update username to non ascii char' do
+ it do
+ fill_in 'user_username', with: '\u3042\u3044'
+ click_button('Save')
+
+ page.within '#error_explanation' do
+ expect(page).to have_content('Username')
+ end
+
+ expect(page).to have_selector(%(form[action="/admin/users/#{user.username}"]))
end
end
end
describe "GET /admin/users/:id/projects" do
+ let(:group) { create(:group) }
+ let!(:project) { create(:project, group: group) }
+
before do
- @group = create(:group)
- @project = create(:project, group: @group)
- @simple_user = create(:user)
- @group.add_developer(@simple_user)
+ group.add_developer(user)
- visit projects_admin_user_path(@simple_user)
+ visit projects_admin_user_path(user)
end
it "lists group projects" do
within(:css, '.append-bottom-default + .panel') do
expect(page).to have_content 'Group projects'
- expect(page).to have_link @group.name, admin_group_path(@group)
+ expect(page).to have_link group.name, admin_group_path(group)
end
end
it 'allows navigation to the group details' do
within(:css, '.append-bottom-default + .panel') do
- click_link @group.name
+ click_link group.name
end
within(:css, 'h3.page-title') do
- expect(page).to have_content "Group: #{@group.name}"
+ expect(page).to have_content "Group: #{group.name}"
end
- expect(page).to have_content @project.name
+ expect(page).to have_content project.name
end
it 'shows the group access level' do
@@ -270,4 +284,99 @@ describe "Admin::Users", feature: true do
expect(page).not_to have_selector('.group_member')
end
end
+
+ describe 'show user attributes' do
+ it do
+ visit admin_users_path
+
+ click_link user.name
+
+ expect(page).to have_content 'Account'
+ expect(page).to have_content 'Personal projects limit'
+ end
+ end
+
+ describe 'remove users secondary email', js: true do
+ let!(:secondary_email) do
+ create :email, email: 'secondary@example.com', user: user
+ end
+
+ it do
+ visit admin_user_path(user.username)
+
+ expect(page).to have_content("Secondary email: #{secondary_email.email}")
+
+ find("#remove_email_#{secondary_email.id}").click
+
+ expect(page).not_to have_content(secondary_email.email)
+ end
+ end
+
+ describe 'show user keys' do
+ let!(:key1) do
+ create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1")
+ end
+
+ let!(:key2) do
+ create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2")
+ end
+
+ it do
+ visit admin_users_path
+
+ click_link user.name
+ click_link 'SSH keys'
+
+ expect(page).to have_content(key1.title)
+ expect(page).to have_content(key2.title)
+
+ click_link key2.title
+
+ expect(page).to have_content(key2.title)
+ expect(page).to have_content(key2.key)
+
+ click_link 'Remove'
+
+ expect(page).not_to have_content(key2.title)
+ end
+ end
+
+ describe 'show user identities' do
+ it 'shows user identities' do
+ visit admin_user_identities_path(user)
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('twitter')
+ end
+ end
+
+ describe 'update user identities' do
+ before do
+ allow(Gitlab::OAuth::Provider).to receive(:providers).and_return([:twitter, :twitter_updated])
+ end
+
+ it 'modifies twitter identity' do
+ visit admin_user_identities_path(user)
+
+ find('.table').find(:link, 'Edit').click
+ fill_in 'identity_extern_uid', with: '654321'
+ select 'twitter_updated', from: 'identity_provider'
+ click_button 'Save changes'
+
+ expect(page).to have_content(user.name)
+ expect(page).to have_content('twitter_updated')
+ expect(page).to have_content('654321')
+ end
+ end
+
+ describe 'remove user with identities' do
+ it 'removes user with twitter identity' do
+ visit admin_user_identities_path(user)
+
+ click_link 'Delete'
+
+ expect(page).to have_content(user.name)
+ expect(page).not_to have_content('twitter')
+ end
+ end
end
diff --git a/spec/features/ci_lint_spec.rb b/spec/features/ci_lint_spec.rb
index 81077f4b005..3ebc432206a 100644
--- a/spec/features/ci_lint_spec.rb
+++ b/spec/features/ci_lint_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'CI Lint' do
+describe 'CI Lint', js: true do
before do
login_as :user
end
@@ -8,7 +8,10 @@ describe 'CI Lint' do
describe 'YAML parsing' do
before do
visit ci_lint_path
- fill_in 'content', with: yaml_content
+ # Ace editor updates a hidden textarea and it happens asynchronously
+ # `sleep 0.1` is actually needed here because of this
+ execute_script("ace.edit('ci-editor').setValue(" + yaml_content.to_json + ");")
+ sleep 0.1
click_on 'Validate'
end
@@ -40,7 +43,7 @@ describe 'CI Lint' do
let(:yaml_content) { 'my yaml content' }
it 'loads previous YAML content after validation' do
- expect(page).to have_field('content', with: 'my yaml content', type: 'textarea')
+ expect(page).to have_field('content', with: 'my yaml content', visible: false, type: 'textarea')
end
end
end
diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb
index e48a2b0c92e..0648c89a5c7 100644
--- a/spec/features/cycle_analytics_spec.rb
+++ b/spec/features/cycle_analytics_spec.rb
@@ -3,7 +3,6 @@ require 'spec_helper'
feature 'Cycle Analytics', feature: true, js: true do
include WaitForAjax
- let(:project) { create(:project) }
let(:user) { create(:user) }
let(:guest) { create(:user) }
let(:project) { create(:project) }
diff --git a/spec/features/dashboard/active_tab_spec.rb b/spec/features/dashboard/active_tab_spec.rb
new file mode 100644
index 00000000000..7d59fcac517
--- /dev/null
+++ b/spec/features/dashboard/active_tab_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Active Tab', feature: true do
+ before do
+ login_as :user
+ end
+
+ shared_examples 'page has active tab' do |title|
+ it "#{title} tab" do
+ expect(page).to have_selector('.nav-sidebar li.active', count: 1)
+ expect(find('.nav-sidebar li.active')).to have_content(title)
+ end
+ end
+
+ context 'on dashboard projects' do
+ before do
+ visit dashboard_projects_path
+ end
+
+ it_behaves_like 'page has active tab', 'Projects'
+ end
+
+ context 'on dashboard issues' do
+ before do
+ visit issues_dashboard_path
+ end
+
+ it_behaves_like 'page has active tab', 'Issues'
+ end
+
+ context 'on dashboard merge requests' do
+ before do
+ visit merge_requests_dashboard_path
+ end
+
+ it_behaves_like 'page has active tab', 'Merge Requests'
+ end
+
+ context 'on dashboard groups' do
+ before do
+ visit dashboard_groups_path
+ end
+
+ it_behaves_like 'page has active tab', 'Groups'
+ end
+end
diff --git a/spec/features/dashboard/archived_projects_spec.rb b/spec/features/dashboard/archived_projects_spec.rb
new file mode 100644
index 00000000000..038c1641be9
--- /dev/null
+++ b/spec/features/dashboard/archived_projects_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Archived Project', feature: true do
+ let(:user) { create :user }
+ let(:project) { create :project}
+ let(:archived_project) { create(:project, :archived) }
+
+ before do
+ project.team << [user, :master]
+ archived_project.team << [user, :master]
+
+ login_as(user)
+
+ visit dashboard_projects_path
+ end
+
+ it 'renders non archived projects' do
+ expect(page).to have_link(project.name)
+ expect(page).not_to have_link(archived_project.name)
+ end
+
+ it 'renders all projects' do
+ click_link 'Show archived projects'
+
+ expect(page).to have_link(project.name)
+ expect(page).to have_link(archived_project.name)
+ end
+end
diff --git a/spec/features/dashboard/group_spec.rb b/spec/features/dashboard/group_spec.rb
new file mode 100644
index 00000000000..d5f8470fab0
--- /dev/null
+++ b/spec/features/dashboard/group_spec.rb
@@ -0,0 +1,20 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Group', feature: true do
+ before do
+ login_as(:user)
+ end
+
+ it 'creates new grpup' do
+ visit dashboard_groups_path
+ click_link 'New Group'
+
+ fill_in 'group_path', with: 'Samurai'
+ fill_in 'group_description', with: 'Tokugawa Shogunate'
+ click_button 'Create group'
+
+ expect(current_path).to eq group_path(Group.find_by(name: 'Samurai'))
+ expect(page).to have_content('Samurai')
+ expect(page).to have_content('Tokugawa Shogunate')
+ end
+end
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
new file mode 100644
index 00000000000..2803f7ec62b
--- /dev/null
+++ b/spec/features/dashboard/help_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+RSpec.describe 'Dashboard Help', feature: true do
+ before do
+ login_as(:user)
+ end
+
+ it 'renders correctly markdown' do
+ visit help_page_path("administration/raketasks/maintenance")
+
+ expect(page).to have_content('Gather information about GitLab and the system it runs on')
+
+ node = find('.documentation h2 a#user-content-check-gitlab-configuration')
+ expect(node[:href]).to eq '#check-gitlab-configuration'
+ expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
+ end
+end
diff --git a/spec/features/expand_collapse_diffs_spec.rb b/spec/features/expand_collapse_diffs_spec.rb
index 3934c936f20..8b3e2fa93a2 100644
--- a/spec/features/expand_collapse_diffs_spec.rb
+++ b/spec/features/expand_collapse_diffs_spec.rb
@@ -4,10 +4,10 @@ feature 'Expand and collapse diffs', js: true, feature: true do
include WaitForAjax
let(:branch) { 'expand-collapse-diffs' }
+ let(:project) { create(:project) }
before do
login_as :admin
- project = create(:project)
# Ensure that undiffable.md is in .gitattributes
project.repository.copy_gitattributes(branch)
@@ -31,6 +31,33 @@ feature 'Expand and collapse diffs', js: true, feature: true do
define_method(file.split('.').first) { file_container(file) }
end
+ it 'should show the diff content with a highlighted line when linking to line' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+
+ visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: "#{large_diff[:id]}_0_1")
+ execute_script('window.location.reload()')
+
+ wait_for_ajax
+
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ expect(large_diff).to have_selector('.hll')
+ end
+
+ it 'should show the diff content when linking to file' do
+ expect(large_diff).not_to have_selector('.code')
+ expect(large_diff).to have_selector('.nothing-here-block')
+
+ visit namespace_project_commit_path(project.namespace, project, project.commit(branch), anchor: large_diff[:id])
+ execute_script('window.location.reload()')
+
+ wait_for_ajax
+
+ expect(large_diff).to have_selector('.code')
+ expect(large_diff).not_to have_selector('.nothing-here-block')
+ end
+
context 'visiting a commit with collapsed diffs' do
it 'shows small diffs immediately' do
expect(small_diff).to have_selector('.code')
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
deleted file mode 100644
index 9dfa5d1de19..00000000000
--- a/spec/features/issues/filter_by_milestone_spec.rb
+++ /dev/null
@@ -1,91 +0,0 @@
-require 'rails_helper'
-
-feature 'Issue filtering by Milestone', feature: true do
- let(:project) { create(:project, :public) }
- let(:milestone) { create(:milestone, project: project) }
-
- scenario 'filters by no Milestone', js: true do
- create(:issue, project: project)
- create(:issue, project: project, milestone: milestone)
-
- visit_issues(project)
- filter_by_milestone(Milestone::None.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'No Milestone')
- expect(page).to have_css('.issue', count: 1)
- end
-
- context 'filters by upcoming milestone', js: true do
- it 'does not show issues with no expiry' do
- create(:issue, project: project)
- create(:issue, project: project, milestone: milestone)
-
- visit_issues(project)
- filter_by_milestone(Milestone::Upcoming.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
- expect(page).to have_css('.issue', count: 0)
- end
-
- it 'shows issues in future' do
- milestone = create(:milestone, project: project, due_date: Date.tomorrow)
- create(:issue, project: project)
- create(:issue, project: project, milestone: milestone)
-
- visit_issues(project)
- filter_by_milestone(Milestone::Upcoming.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
- expect(page).to have_css('.issue', count: 1)
- end
-
- it 'does not show issues in past' do
- milestone = create(:milestone, project: project, due_date: Date.yesterday)
- create(:issue, project: project)
- create(:issue, project: project, milestone: milestone)
-
- visit_issues(project)
- filter_by_milestone(Milestone::Upcoming.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: 'Upcoming')
- expect(page).to have_css('.issue', count: 0)
- end
- end
-
- scenario 'filters by a specific Milestone', js: true do
- create(:issue, project: project, milestone: milestone)
- create(:issue, project: project)
-
- visit_issues(project)
- filter_by_milestone(milestone.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title)
- expect(page).to have_css('.issue', count: 1)
- end
-
- context 'when milestone has single quotes in title' do
- background do
- milestone.update(name: "rock 'n' roll")
- end
-
- scenario 'filters by a specific Milestone', js: true do
- create(:issue, project: project, milestone: milestone)
- create(:issue, project: project)
-
- visit_issues(project)
- filter_by_milestone(milestone.title)
-
- expect(page).to have_css('.milestone-filter .dropdown-toggle-text', text: milestone.title)
- expect(page).to have_css('.issue', count: 1)
- end
- end
-
- def visit_issues(project)
- visit namespace_project_issues_path(project.namespace, project)
- end
-
- def filter_by_milestone(title)
- find(".js-milestone-select").click
- find(".milestone-filter .dropdown-content a", text: title).click
- end
-end
diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
new file mode 100644
index 00000000000..6f6a2532c04
--- /dev/null
+++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb
@@ -0,0 +1,166 @@
+require 'rails_helper'
+
+describe 'Dropdown assignee', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user, name: 'administrator', username: 'root') }
+ let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
+ let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
+ let(:filtered_search) { find('.filtered-search') }
+ let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
+
+ def send_keys_to_filtered_search(input)
+ input.split("").each do |i|
+ filtered_search.send_keys(i)
+ sleep 5
+ wait_for_ajax
+ end
+ end
+
+ def dropdown_assignee_size
+ page.all('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item').size
+ end
+
+ def click_assignee(text)
+ find('#js-dropdown-assignee .filter-dropdown .filter-dropdown-item', text: text).click
+ end
+
+ before do
+ project.team << [user, :master]
+ project.team << [user_john, :master]
+ project.team << [user_jacob, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'behavior' do
+ it 'opens when the search bar has assignee:' do
+ filtered_search.set('assignee:')
+
+ expect(page).to have_css(js_dropdown_assignee, visible: true)
+ end
+
+ it 'closes when the search bar is unfocused' do
+ find('body').click()
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ end
+
+ it 'should show loading indicator when opened' do
+ filtered_search.set('assignee:')
+
+ expect(page).to have_css('#js-dropdown-assignee .filter-dropdown-loading', visible: true)
+ end
+
+ it 'should hide loading indicator when loaded' do
+ send_keys_to_filtered_search('assignee:')
+
+ expect(page).not_to have_css('#js-dropdown-assignee .filter-dropdown-loading')
+ end
+
+ it 'should load all the assignees when opened' do
+ send_keys_to_filtered_search('assignee:')
+
+ expect(dropdown_assignee_size).to eq(3)
+ end
+ end
+
+ describe 'filtering' do
+ before do
+ send_keys_to_filtered_search('assignee:')
+ end
+
+ it 'filters by name' do
+ send_keys_to_filtered_search('j')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+
+ it 'filters by case insensitive name' do
+ send_keys_to_filtered_search('J')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+
+ it 'filters by username with symbol' do
+ send_keys_to_filtered_search('@ot')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+
+ it 'filters by case insensitive username with symbol' do
+ send_keys_to_filtered_search('@OT')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+
+ it 'filters by username without symbol' do
+ send_keys_to_filtered_search('ot')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+
+ it 'filters by case insensitive username without symbol' do
+ send_keys_to_filtered_search('OT')
+
+ expect(dropdown_assignee_size).to eq(2)
+ end
+ end
+
+ describe 'selecting from dropdown' do
+ before do
+ filtered_search.set('assignee:')
+ end
+
+ it 'fills in the assignee username when the assignee has not been filtered' do
+ click_assignee(user_jacob.name)
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect(filtered_search.value).to eq("assignee:@#{user_jacob.username}")
+ end
+
+ it 'fills in the assignee username when the assignee has been filtered' do
+ send_keys_to_filtered_search('roo')
+ click_assignee(user.name)
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect(filtered_search.value).to eq("assignee:@#{user.username}")
+ end
+
+ it 'selects `no assignee`' do
+ find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click
+
+ expect(page).to have_css(js_dropdown_assignee, visible: false)
+ expect(filtered_search.value).to eq("assignee:none")
+ end
+ end
+
+ describe 'input has existing content' do
+ it 'opens assignee dropdown with existing search term' do
+ filtered_search.set('searchTerm assignee:')
+
+ expect(page).to have_css(js_dropdown_assignee, visible: true)
+ end
+
+ it 'opens assignee dropdown with existing author' do
+ filtered_search.set('author:@user assignee:')
+
+ expect(page).to have_css(js_dropdown_assignee, visible: true)
+ end
+
+ it 'opens assignee dropdown with existing label' do
+ filtered_search.set('label:~bug assignee:')
+
+ expect(page).to have_css(js_dropdown_assignee, visible: true)
+ end
+
+ it 'opens assignee dropdown with existing milestone' do
+ filtered_search.set('milestone:%v1.0 assignee:')
+
+ expect(page).to have_css(js_dropdown_assignee, visible: true)
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/dropdown_author_spec.rb b/spec/features/issues/filtered_search/dropdown_author_spec.rb
new file mode 100644
index 00000000000..60a86cc93d4
--- /dev/null
+++ b/spec/features/issues/filtered_search/dropdown_author_spec.rb
@@ -0,0 +1,154 @@
+require 'rails_helper'
+
+describe 'Dropdown author', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user, name: 'administrator', username: 'root') }
+ let!(:user_john) { create(:user, name: 'John', username: 'th0mas') }
+ let!(:user_jacob) { create(:user, name: 'Jacob', username: 'otter32') }
+ let(:filtered_search) { find('.filtered-search') }
+ let(:js_dropdown_author) { '#js-dropdown-author' }
+
+ def send_keys_to_filtered_search(input)
+ input.split("").each do |i|
+ filtered_search.send_keys(i)
+ sleep 5
+ wait_for_ajax
+ end
+ end
+
+ def dropdown_author_size
+ page.all('#js-dropdown-author .filter-dropdown .filter-dropdown-item').size
+ end
+
+ def click_author(text)
+ find('#js-dropdown-author .filter-dropdown .filter-dropdown-item', text: text).click
+ end
+
+ before do
+ project.team << [user, :master]
+ project.team << [user_john, :master]
+ project.team << [user_jacob, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'behavior' do
+ it 'opens when the search bar has author:' do
+ filtered_search.set('author:')
+
+ expect(page).to have_css(js_dropdown_author, visible: true)
+ end
+
+ it 'closes when the search bar is unfocused' do
+ find('body').click()
+
+ expect(page).to have_css(js_dropdown_author, visible: false)
+ end
+
+ it 'should show loading indicator when opened' do
+ filtered_search.set('author:')
+
+ expect(page).to have_css('#js-dropdown-author .filter-dropdown-loading', visible: true)
+ end
+
+ it 'should hide loading indicator when loaded' do
+ send_keys_to_filtered_search('author:')
+
+ expect(page).not_to have_css('#js-dropdown-author .filter-dropdown-loading')
+ end
+
+ it 'should load all the authors when opened' do
+ send_keys_to_filtered_search('author:')
+
+ expect(dropdown_author_size).to eq(3)
+ end
+ end
+
+ describe 'filtering' do
+ before do
+ filtered_search.set('author')
+ send_keys_to_filtered_search(':')
+ end
+
+ it 'filters by name' do
+ send_keys_to_filtered_search('ja')
+
+ expect(dropdown_author_size).to eq(1)
+ end
+
+ it 'filters by case insensitive name' do
+ send_keys_to_filtered_search('Ja')
+
+ expect(dropdown_author_size).to eq(1)
+ end
+
+ it 'filters by username with symbol' do
+ send_keys_to_filtered_search('@ot')
+
+ expect(dropdown_author_size).to eq(2)
+ end
+
+ it 'filters by username without symbol' do
+ send_keys_to_filtered_search('ot')
+
+ expect(dropdown_author_size).to eq(2)
+ end
+
+ it 'filters by case insensitive username without symbol' do
+ send_keys_to_filtered_search('OT')
+
+ expect(dropdown_author_size).to eq(2)
+ end
+ end
+
+ describe 'selecting from dropdown' do
+ before do
+ filtered_search.set('author')
+ send_keys_to_filtered_search(':')
+ end
+
+ it 'fills in the author username when the author has not been filtered' do
+ click_author(user_jacob.name)
+
+ expect(page).to have_css(js_dropdown_author, visible: false)
+ expect(filtered_search.value).to eq("author:@#{user_jacob.username}")
+ end
+
+ it 'fills in the author username when the author has been filtered' do
+ click_author(user.name)
+
+ expect(page).to have_css(js_dropdown_author, visible: false)
+ expect(filtered_search.value).to eq("author:@#{user.username}")
+ end
+ end
+
+ describe 'input has existing content' do
+ it 'opens author dropdown with existing search term' do
+ filtered_search.set('searchTerm author:')
+
+ expect(page).to have_css(js_dropdown_author, visible: true)
+ end
+
+ it 'opens author dropdown with existing assignee' do
+ filtered_search.set('assignee:@user author:')
+
+ expect(page).to have_css(js_dropdown_author, visible: true)
+ end
+
+ it 'opens author dropdown with existing label' do
+ filtered_search.set('label:~bug author:')
+
+ expect(page).to have_css(js_dropdown_author, visible: true)
+ end
+
+ it 'opens author dropdown with existing milestone' do
+ filtered_search.set('milestone:%v1.0 author:')
+
+ expect(page).to have_css(js_dropdown_author, visible: true)
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/dropdown_hint_spec.rb b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
new file mode 100644
index 00000000000..04dd54ab459
--- /dev/null
+++ b/spec/features/issues/filtered_search/dropdown_hint_spec.rb
@@ -0,0 +1,134 @@
+require 'rails_helper'
+
+describe 'Dropdown hint', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user) }
+ let(:filtered_search) { find('.filtered-search') }
+ let(:js_dropdown_hint) { '#js-dropdown-hint' }
+
+ def dropdown_hint_size
+ page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size
+ end
+
+ def click_hint(text)
+ find('#js-dropdown-hint .filter-dropdown .filter-dropdown-item', text: text).click
+ end
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'behavior' do
+ before do
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ filtered_search.click
+ end
+
+ it 'opens when the search bar is first focused' do
+ expect(page).to have_css(js_dropdown_hint, visible: true)
+ end
+
+ it 'closes when the search bar is unfocused' do
+ find('body').click
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ end
+ end
+
+ describe 'filtering' do
+ it 'does not filter `Keep typing and press Enter`' do
+ filtered_search.set('randomtext')
+
+ expect(page).to have_css(js_dropdown_hint, text: 'Keep typing and press Enter', visible: false)
+ expect(dropdown_hint_size).to eq(0)
+ end
+
+ it 'filters with text' do
+ filtered_search.set('a')
+
+ expect(dropdown_hint_size).to eq(3)
+ end
+ end
+
+ describe 'selecting from dropdown with no input' do
+ before do
+ filtered_search.click
+ end
+
+ it 'opens the author dropdown when you click on author' do
+ click_hint('author')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-author', visible: true)
+ expect(filtered_search.value).to eq('author:')
+ end
+
+ it 'opens the assignee dropdown when you click on assignee' do
+ click_hint('assignee')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-assignee', visible: true)
+ expect(filtered_search.value).to eq('assignee:')
+ end
+
+ it 'opens the milestone dropdown when you click on milestone' do
+ click_hint('milestone')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-milestone', visible: true)
+ expect(filtered_search.value).to eq('milestone:')
+ end
+
+ it 'opens the label dropdown when you click on label' do
+ click_hint('label')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-label', visible: true)
+ expect(filtered_search.value).to eq('label:')
+ end
+ end
+
+ describe 'selecting from dropdown with some input' do
+ it 'opens the author dropdown when you click on author' do
+ filtered_search.set('auth')
+ click_hint('author')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-author', visible: true)
+ expect(filtered_search.value).to eq('author:')
+ end
+
+ it 'opens the assignee dropdown when you click on assignee' do
+ filtered_search.set('assign')
+ click_hint('assignee')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-assignee', visible: true)
+ expect(filtered_search.value).to eq('assignee:')
+ end
+
+ it 'opens the milestone dropdown when you click on milestone' do
+ filtered_search.set('mile')
+ click_hint('milestone')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-milestone', visible: true)
+ expect(filtered_search.value).to eq('milestone:')
+ end
+
+ it 'opens the label dropdown when you click on label' do
+ filtered_search.set('lab')
+ click_hint('label')
+
+ expect(page).to have_css(js_dropdown_hint, visible: false)
+ expect(page).to have_css('#js-dropdown-label', visible: true)
+ expect(filtered_search.value).to eq('label:')
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/dropdown_label_spec.rb b/spec/features/issues/filtered_search/dropdown_label_spec.rb
new file mode 100644
index 00000000000..89c144141c9
--- /dev/null
+++ b/spec/features/issues/filtered_search/dropdown_label_spec.rb
@@ -0,0 +1,242 @@
+require 'rails_helper'
+
+describe 'Dropdown label', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user) }
+ let!(:bug_label) { create(:label, project: project, title: 'bug') }
+ let!(:uppercase_label) { create(:label, project: project, title: 'BUG') }
+ let!(:two_words_label) { create(:label, project: project, title: 'High Priority') }
+ let!(:wont_fix_label) { create(:label, project: project, title: 'Won"t Fix') }
+ let!(:wont_fix_single_label) { create(:label, project: project, title: 'Won\'t Fix') }
+ let!(:special_label) { create(:label, project: project, title: '!@#$%^+&*()')}
+ let!(:long_label) { create(:label, project: project, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title')}
+ let(:filtered_search) { find('.filtered-search') }
+ let(:js_dropdown_label) { '#js-dropdown-label' }
+
+ def send_keys_to_filtered_search(input)
+ input.split("").each do |i|
+ filtered_search.send_keys(i)
+ sleep 3
+ wait_for_ajax
+ sleep 3
+ end
+ end
+
+ def dropdown_label_size
+ page.all('#js-dropdown-label .filter-dropdown .filter-dropdown-item').size
+ end
+
+ def click_label(text)
+ find('#js-dropdown-label .filter-dropdown .filter-dropdown-item', text: text).click
+ end
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'behavior' do
+ it 'opens when the search bar has label:' do
+ filtered_search.set('label:')
+
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+
+ it 'closes when the search bar is unfocused' do
+ find('body').click()
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ end
+
+ it 'should show loading indicator when opened' do
+ filtered_search.set('label:')
+
+ expect(page).to have_css('#js-dropdown-label .filter-dropdown-loading', visible: true)
+ end
+
+ it 'should hide loading indicator when loaded' do
+ send_keys_to_filtered_search('label:')
+
+ expect(page).not_to have_css('#js-dropdown-label .filter-dropdown-loading')
+ end
+
+ it 'should load all the labels when opened' do
+ send_keys_to_filtered_search('label:')
+
+ expect(dropdown_label_size).to be > 0
+ end
+ end
+
+ describe 'filtering' do
+ before do
+ filtered_search.set('label')
+ end
+
+ it 'filters by name' do
+ send_keys_to_filtered_search(':b')
+
+ expect(dropdown_label_size).to eq(2)
+ end
+
+ it 'filters by case insensitive name' do
+ send_keys_to_filtered_search(':B')
+
+ expect(dropdown_label_size).to eq(2)
+ end
+
+ it 'filters by name with symbol' do
+ send_keys_to_filtered_search(':~bu')
+
+ expect(dropdown_label_size).to eq(2)
+ end
+
+ it 'filters by case insensitive name with symbol' do
+ send_keys_to_filtered_search(':~BU')
+
+ expect(dropdown_label_size).to eq(2)
+ end
+
+ it 'filters by multiple words' do
+ send_keys_to_filtered_search(':Hig')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by multiple words with symbol' do
+ send_keys_to_filtered_search(':~Hig')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by multiple words containing single quotes' do
+ send_keys_to_filtered_search(':won\'t')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by multiple words containing single quotes with symbol' do
+ send_keys_to_filtered_search(':~won\'t')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by multiple words containing double quotes' do
+ send_keys_to_filtered_search(':won"t')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by multiple words containing double quotes with symbol' do
+ send_keys_to_filtered_search(':~won"t')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by special characters' do
+ send_keys_to_filtered_search(':^+')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+
+ it 'filters by special characters with symbol' do
+ send_keys_to_filtered_search(':~^+')
+
+ expect(dropdown_label_size).to eq(1)
+ end
+ end
+
+ describe 'selecting from dropdown' do
+ before do
+ filtered_search.set('label:')
+ end
+
+ it 'fills in the label name when the label has not been filled' do
+ click_label(bug_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~#{bug_label.title}")
+ end
+
+ it 'fills in the label name when the label is partially filled' do
+ send_keys_to_filtered_search('bu')
+ click_label(bug_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~#{bug_label.title}")
+ end
+
+ it 'fills in the label name that contains multiple words' do
+ click_label(two_words_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~\"#{two_words_label.title}\"")
+ end
+
+ it 'fills in the label name that contains multiple words and is very long' do
+ click_label(long_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~\"#{long_label.title}\"")
+ end
+
+ it 'fills in the label name that contains double quotes' do
+ click_label(wont_fix_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~'#{wont_fix_label.title}'")
+ end
+
+ it 'fills in the label name with the correct capitalization' do
+ click_label(uppercase_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~#{uppercase_label.title}")
+ end
+
+ it 'fills in the label name with special characters' do
+ click_label(special_label.title)
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:~#{special_label.title}")
+ end
+
+ it 'selects `no label`' do
+ find('#js-dropdown-label .filter-dropdown-item', text: 'No Label').click
+
+ expect(page).to have_css(js_dropdown_label, visible: false)
+ expect(filtered_search.value).to eq("label:none")
+ end
+ end
+
+ describe 'input has existing content' do
+ it 'opens label dropdown with existing search term' do
+ filtered_search.set('searchTerm label:')
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+
+ it 'opens label dropdown with existing author' do
+ filtered_search.set('author:@person label:')
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+
+ it 'opens label dropdown with existing assignee' do
+ filtered_search.set('assignee:@person label:')
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+
+ it 'opens label dropdown with existing label' do
+ filtered_search.set('label:~urgent label:')
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+
+ it 'opens label dropdown with existing milestone' do
+ filtered_search.set('milestone:%v2.0 label:')
+ expect(page).to have_css(js_dropdown_label, visible: true)
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/dropdown_milestone_spec.rb b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
new file mode 100644
index 00000000000..e5a271b663f
--- /dev/null
+++ b/spec/features/issues/filtered_search/dropdown_milestone_spec.rb
@@ -0,0 +1,222 @@
+require 'rails_helper'
+
+describe 'Dropdown milestone', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user) }
+ let!(:milestone) { create(:milestone, title: 'v1.0', project: project) }
+ let!(:uppercase_milestone) { create(:milestone, title: 'CAP_MILESTONE', project: project) }
+ let!(:two_words_milestone) { create(:milestone, title: 'Future Plan', project: project) }
+ let!(:wont_fix_milestone) { create(:milestone, title: 'Won"t Fix', project: project) }
+ let!(:special_milestone) { create(:milestone, title: '!@#$%^&*(+)', project: project) }
+ let!(:long_milestone) { create(:milestone, title: 'this is a very long title this is a very long title this is a very long title this is a very long title this is a very long title', project: project) }
+
+ let(:filtered_search) { find('.filtered-search') }
+ let(:js_dropdown_milestone) { '#js-dropdown-milestone' }
+
+ def send_keys_to_filtered_search(input)
+ input.split("").each do |i|
+ filtered_search.send_keys(i)
+ sleep 3
+ wait_for_ajax
+ sleep 3
+ end
+ end
+
+ def dropdown_milestone_size
+ page.all('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item').size
+ end
+
+ def click_milestone(text)
+ find('#js-dropdown-milestone .filter-dropdown .filter-dropdown-item', text: text).click
+ end
+
+ def click_static_milestone(text)
+ find('#js-dropdown-milestone .filter-dropdown-item', text: text).click
+ end
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'behavior' do
+ it 'opens when the search bar has milestone:' do
+ filtered_search.set('milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+
+ it 'closes when the search bar is unfocused' do
+ find('body').click()
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ end
+
+ it 'should show loading indicator when opened' do
+ filtered_search.set('milestone:')
+
+ expect(page).to have_css('#js-dropdown-milestone .filter-dropdown-loading', visible: true)
+ end
+
+ it 'should hide loading indicator when loaded' do
+ send_keys_to_filtered_search('milestone:')
+
+ expect(page).not_to have_css('#js-dropdown-milestone .filter-dropdown-loading')
+ end
+
+ it 'should load all the milestones when opened' do
+ send_keys_to_filtered_search('milestone:')
+
+ expect(dropdown_milestone_size).to be > 0
+ end
+ end
+
+ describe 'filtering' do
+ before do
+ filtered_search.set('milestone')
+ end
+
+ it 'filters by name' do
+ send_keys_to_filtered_search(':v1')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+
+ it 'filters by case insensitive name' do
+ send_keys_to_filtered_search(':V1')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+
+ it 'filters by name with symbol' do
+ send_keys_to_filtered_search(':%v1')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+
+ it 'filters by case insensitive name with symbol' do
+ send_keys_to_filtered_search(':%V1')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+
+ it 'filters by special characters' do
+ send_keys_to_filtered_search(':(+')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+
+ it 'filters by special characters with symbol' do
+ send_keys_to_filtered_search(':%(+')
+
+ expect(dropdown_milestone_size).to eq(1)
+ end
+ end
+
+ describe 'selecting from dropdown' do
+ before do
+ filtered_search.set('milestone:')
+ end
+
+ it 'fills in the milestone name when the milestone has not been filled' do
+ click_milestone(milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%#{milestone.title}")
+ end
+
+ it 'fills in the milestone name when the milestone is partially filled' do
+ send_keys_to_filtered_search('v')
+ click_milestone(milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%#{milestone.title}")
+ end
+
+ it 'fills in the milestone name that contains multiple words' do
+ click_milestone(two_words_milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%\"#{two_words_milestone.title}\"")
+ end
+
+ it 'fills in the milestone name that contains multiple words and is very long' do
+ click_milestone(long_milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%\"#{long_milestone.title}\"")
+ end
+
+ it 'fills in the milestone name that contains double quotes' do
+ click_milestone(wont_fix_milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%'#{wont_fix_milestone.title}'")
+ end
+
+ it 'fills in the milestone name with the correct capitalization' do
+ click_milestone(uppercase_milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%#{uppercase_milestone.title}")
+ end
+
+ it 'fills in the milestone name with special characters' do
+ click_milestone(special_milestone.title)
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:%#{special_milestone.title}")
+ end
+
+ it 'selects `no milestone`' do
+ click_static_milestone('No Milestone')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:none")
+ end
+
+ it 'selects `upcoming milestone`' do
+ click_static_milestone('Upcoming')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: false)
+ expect(filtered_search.value).to eq("milestone:upcoming")
+ end
+ end
+
+ describe 'input has existing content' do
+ it 'opens milestone dropdown with existing search term' do
+ filtered_search.set('searchTerm milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+
+ it 'opens milestone dropdown with existing author' do
+ filtered_search.set('author:@john milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+
+ it 'opens milestone dropdown with existing assignee' do
+ filtered_search.set('assignee:@john milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+
+ it 'opens milestone dropdown with existing label' do
+ filtered_search.set('label:~important milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+
+ it 'opens milestone dropdown with existing milestone' do
+ filtered_search.set('milestone:%100 milestone:')
+
+ expect(page).to have_css(js_dropdown_milestone, visible: true)
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/filter_issues_spec.rb b/spec/features/issues/filtered_search/filter_issues_spec.rb
new file mode 100644
index 00000000000..ead43d6784a
--- /dev/null
+++ b/spec/features/issues/filtered_search/filter_issues_spec.rb
@@ -0,0 +1,759 @@
+require 'rails_helper'
+
+describe 'Filter issues', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, group: group) }
+ let!(:user) { create(:user) }
+ let!(:user2) { create(:user) }
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:label) { create(:label, project: project) }
+ let!(:wontfix) { create(:label, project: project, title: "Won't fix") }
+
+ let!(:bug_label) { create(:label, project: project, title: 'bug') }
+ let!(:caps_sensitive_label) { create(:label, project: project, title: 'CAPS_sensitive') }
+ let!(:milestone) { create(:milestone, title: "8", project: project) }
+ let!(:multiple_words_label) { create(:label, project: project, title: "Two words") }
+
+ let!(:closed_issue) { create(:issue, title: 'bug that is closed', project: project, state: :closed) }
+ let(:filtered_search) { find('.filtered-search') }
+
+ def input_filtered_search(search_term)
+ filtered_search.set(search_term)
+ filtered_search.send_keys(:enter)
+ end
+
+ def expect_filtered_search_input(input)
+ expect(find('.filtered-search').value).to eq(input)
+ end
+
+ def expect_no_issues_list
+ page.within '.issues-list' do
+ expect(page).not_to have_selector('.issue')
+ end
+ end
+
+ def expect_issues_list_count(open_count, closed_count = 0)
+ all_count = open_count + closed_count
+
+ expect(page).to have_issuable_counts(open: open_count, closed: closed_count, all: all_count)
+ page.within '.issues-list' do
+ expect(page).to have_selector('.issue', count: open_count)
+ end
+ end
+
+ before do
+ project.team << [user, :master]
+ project.team << [user2, :master]
+ group.add_developer(user)
+ group.add_developer(user2)
+ login_as(user)
+ create(:issue, project: project)
+
+ create(:issue, title: "Bug report 1", project: project)
+ create(:issue, title: "Bug report 2", project: project)
+ create(:issue, title: "issue with 'single quotes'", project: project)
+ create(:issue, title: "issue with \"double quotes\"", project: project)
+ create(:issue, title: "issue with !@\#{$%^&*()-+", project: project)
+ create(:issue, title: "issue by assignee", project: project, milestone: milestone, author: user, assignee: user)
+ create(:issue, title: "issue by assignee with searchTerm", project: project, milestone: milestone, author: user, assignee: user)
+
+ issue = create(:issue,
+ title: "Bug 2",
+ project: project,
+ milestone: milestone,
+ author: user,
+ assignee: user)
+ issue.labels << bug_label
+
+ issue_with_caps_label = create(:issue,
+ title: "issue by assignee with searchTerm and label",
+ project: project,
+ milestone: milestone,
+ author: user,
+ assignee: user)
+ issue_with_caps_label.labels << caps_sensitive_label
+
+ issue_with_everything = create(:issue,
+ title: "Bug report with everything you thought was possible",
+ project: project,
+ milestone: milestone,
+ author: user,
+ assignee: user)
+ issue_with_everything.labels << bug_label
+ issue_with_everything.labels << caps_sensitive_label
+
+ multiple_words_label_issue = create(:issue, title: "Issue with multiple words label", project: project)
+ multiple_words_label_issue.labels << multiple_words_label
+
+ future_milestone = create(:milestone, title: "future", project: project, due_date: Time.now + 1.month)
+
+ create(:issue,
+ title: "Issue with future milestone",
+ milestone: future_milestone,
+ project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ describe 'filter issues by author' do
+ context 'only author' do
+ it 'filters issues by searched author' do
+ input_filtered_search("author:@#{user.username}")
+
+ expect_issues_list_count(5)
+ end
+
+ it 'filters issues by invalid author' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+
+ it 'filters issues by multiple authors' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+
+ context 'author with other filters' do
+ it 'filters issues by searched author and text' do
+ search = "author:@#{user.username} issue"
+ input_filtered_search(search)
+
+ expect_issues_list_count(3)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched author, assignee and text' do
+ search = "author:@#{user.username} assignee:@#{user.username} issue"
+ input_filtered_search(search)
+
+ expect_issues_list_count(3)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched author, assignee, label, and text' do
+ search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} issue"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched author, assignee, label, milestone and text' do
+ search = "author:@#{user.username} assignee:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} issue"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ it 'sorting' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+
+ describe 'filter issues by assignee' do
+ context 'only assignee' do
+ it 'filters issues by searched assignee' do
+ search = "assignee:@#{user.username}"
+ input_filtered_search(search)
+
+ expect_issues_list_count(5)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by no assignee' do
+ search = "assignee:none"
+ input_filtered_search(search)
+
+ expect_issues_list_count(8, 1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by invalid assignee' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+
+ it 'filters issues by multiple assignees' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+
+ context 'assignee with other filters' do
+ it 'filters issues by searched assignee and text' do
+ search = "assignee:@#{user.username} searchTerm"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched assignee, author and text' do
+ search = "assignee:@#{user.username} author:@#{user.username} searchTerm"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched assignee, author, label, text' do
+ search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} searchTerm"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched assignee, author, label, milestone and text' do
+ search = "assignee:@#{user.username} author:@#{user.username} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} searchTerm"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'sorting' do
+ it 'sorts' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+ end
+
+ describe 'filter issues by label' do
+ context 'only label' do
+ it 'filters issues by searched label' do
+ search = "label:~#{bug_label.title}"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by no label' do
+ search = "label:none"
+ input_filtered_search(search)
+
+ expect_issues_list_count(9, 1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by invalid label' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+
+ it 'filters issues by multiple labels' do
+ search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title}"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by label containing special characters' do
+ special_label = create(:label, project: project, title: '!@#{$%^&*()-+[]<>?/:{}|\}')
+ special_issue = create(:issue, title: "Issue with special character label", project: project)
+ special_issue.labels << special_label
+
+ search = "label:~#{special_label.title}"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'does not show issues' do
+ new_label = create(:label, project: project, title: "new_label")
+
+ search = "label:~#{new_label.title}"
+ input_filtered_search(search)
+
+ expect_no_issues_list()
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'label with multiple words' do
+ it 'special characters' do
+ special_multiple_label = create(:label, project: project, title: "Utmost |mp0rt@nce")
+ special_multiple_issue = create(:issue, title: "Issue with special character multiple words label", project: project)
+ special_multiple_issue.labels << special_multiple_label
+
+ search = "label:~'#{special_multiple_label.title}'"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+
+ # filtered search defaults quotations to double quotes
+ expect_filtered_search_input("label:~\"#{special_multiple_label.title}\"")
+ end
+
+ it 'single quotes' do
+ search = "label:~'#{multiple_words_label.title}'"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("label:~\"#{multiple_words_label.title}\"")
+ end
+
+ it 'double quotes' do
+ search = "label:~\"#{multiple_words_label.title}\""
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'single quotes containing double quotes' do
+ double_quotes_label = create(:label, project: project, title: 'won"t fix')
+ double_quotes_label_issue = create(:issue, title: "Issue with double quotes label", project: project)
+ double_quotes_label_issue.labels << double_quotes_label
+
+ search = "label:~'#{double_quotes_label.title}'"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'double quotes containing single quotes' do
+ single_quotes_label = create(:label, project: project, title: "won't fix")
+ single_quotes_label_issue = create(:issue, title: "Issue with single quotes label", project: project)
+ single_quotes_label_issue.labels << single_quotes_label
+
+ search = "label:~\"#{single_quotes_label.title}\""
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'label with other filters' do
+ it 'filters issues by searched label and text' do
+ search = "label:~#{caps_sensitive_label.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, author and text' do
+ search = "label:~#{caps_sensitive_label.title} author:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, author, assignee and text' do
+ search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, author, assignee, milestone and text' do
+ search = "label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'multiple labels with other filters' do
+ it 'filters issues by searched label, label2, and text' do
+ search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, label2, author and text' do
+ search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, label2, author, assignee and text' do
+ search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched label, label2, author, assignee, milestone and text' do
+ search = "label:~#{bug_label.title} label:~#{caps_sensitive_label.title} author:@#{user.username} assignee:@#{user.username} milestone:%#{milestone.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'issue label clicked' do
+ before do
+ find('.issues-list .issue .issue-info a .label', text: multiple_words_label.title).click
+ sleep 1
+ end
+
+ it 'filters' do
+ expect_issues_list_count(1)
+ end
+
+ it 'displays in search bar' do
+ expect(find('.filtered-search').value).to eq("label:~\"#{multiple_words_label.title}\"")
+ end
+ end
+
+ context 'sorting' do
+ it 'sorts' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+ end
+
+ describe 'filter issues by milestone' do
+ context 'only milestone' do
+ it 'filters issues by searched milestone' do
+ input_filtered_search("milestone:%#{milestone.title}")
+
+ expect_issues_list_count(5)
+ end
+
+ it 'filters issues by no milestone' do
+ input_filtered_search("milestone:none")
+
+ expect_issues_list_count(7, 1)
+ end
+
+ it 'filters issues by upcoming milestones' do
+ input_filtered_search("milestone:upcoming")
+
+ expect_issues_list_count(1)
+ end
+
+ it 'filters issues by invalid milestones' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+
+ it 'filters issues by multiple milestones' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+
+ it 'filters issues by milestone containing special characters' do
+ special_milestone = create(:milestone, title: '!@\#{$%^&*()}', project: project)
+ create(:issue, title: "Issue with special character milestone", project: project, milestone: special_milestone)
+
+ search = "milestone:%#{special_milestone.title}"
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'does not show issues' do
+ new_milestone = create(:milestone, title: "new", project: project)
+
+ search = "milestone:%#{new_milestone.title}"
+ input_filtered_search(search)
+
+ expect_no_issues_list()
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'milestone with other filters' do
+ it 'filters issues by searched milestone and text' do
+ search = "milestone:%#{milestone.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched milestone, author and text' do
+ search = "milestone:%#{milestone.title} author:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched milestone, author, assignee and text' do
+ search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched milestone, author, assignee, label and text' do
+ search = "milestone:%#{milestone.title} author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug"
+ input_filtered_search(search)
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'sorting' do
+ it 'sorts' do
+ pending('to be tested, issue #26546')
+ expect(true).to be(false)
+ end
+ end
+ end
+
+ describe 'filter issues by text' do
+ context 'only text' do
+ it 'filters issues by searched text' do
+ search = 'Bug'
+ input_filtered_search(search)
+
+ expect_issues_list_count(4, 1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by multiple searched text' do
+ search = 'Bug report'
+ input_filtered_search(search)
+
+ expect_issues_list_count(3)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by case insensitive searched text' do
+ search = 'bug report'
+ input_filtered_search(search)
+
+ expect_issues_list_count(3)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched text containing single quotes' do
+ search = '\'single quotes\''
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched text containing double quotes' do
+ search = '"double quotes"'
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'filters issues by searched text containing special characters' do
+ search = '!@#{$%^&*()-+'
+ input_filtered_search(search)
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input(search)
+ end
+
+ it 'does not show any issues' do
+ search = 'testing'
+ input_filtered_search(search)
+
+ expect_no_issues_list()
+ expect_filtered_search_input(search)
+ end
+ end
+
+ context 'searched text with other filters' do
+ it 'filters issues by searched text and author' do
+ input_filtered_search("bug author:@#{user.username}")
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input("author:@#{user.username} bug")
+ end
+
+ it 'filters issues by searched text, author and more text' do
+ input_filtered_search("bug author:@#{user.username} report")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} bug report")
+ end
+
+ it 'filters issues by searched text, author and assignee' do
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username}")
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug")
+ end
+
+ it 'filters issues by searched text, author, more text and assignee' do
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username}")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report")
+ end
+
+ it 'filters issues by searched text, author, more text, assignee and even more text' do
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} bug report with")
+ end
+
+ it 'filters issues by searched text, author, assignee and label' do
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title}")
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug")
+ end
+
+ it 'filters issues by searched text, author, text, assignee, text, label and text' do
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} bug report with everything")
+ end
+
+ it 'filters issues by searched text, author, assignee, label and milestone' do
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title}")
+
+ expect_issues_list_count(2)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug")
+ end
+
+ it 'filters issues by searched text, author, text, assignee, text, label, text, milestone and text' do
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything milestone:%#{milestone.title} you")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} milestone:%#{milestone.title} bug report with everything you")
+ end
+
+ it 'filters issues by searched text, author, assignee, multiple labels and milestone' do
+ input_filtered_search("bug author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title}")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug")
+ end
+
+ it 'filters issues by searched text, author, text, assignee, text, label1, text, label2, text, milestone and text' do
+ input_filtered_search("bug author:@#{user.username} report assignee:@#{user.username} with label:~#{bug_label.title} everything label:~#{caps_sensitive_label.title} you milestone:%#{milestone.title} thought")
+
+ expect_issues_list_count(1)
+ expect_filtered_search_input("author:@#{user.username} assignee:@#{user.username} label:~#{bug_label.title} label:~#{caps_sensitive_label.title} milestone:%#{milestone.title} bug report with everything you thought")
+ end
+ end
+
+ context 'sorting' do
+ it 'sorts by oldest updated' do
+ create(:issue,
+ title: '3 days ago',
+ project: project,
+ author: user,
+ created_at: 3.days.ago,
+ updated_at: 3.days.ago)
+
+ old_issue = create(:issue,
+ title: '5 days ago',
+ project: project,
+ author: user,
+ created_at: 5.days.ago,
+ updated_at: 5.days.ago)
+
+ input_filtered_search('days ago')
+
+ expect_issues_list_count(2)
+
+ sort_toggle = find('.filtered-search-container .dropdown-toggle')
+ sort_toggle.click
+
+ find('.filtered-search-container .dropdown-menu li a', text: 'Oldest updated').click
+ wait_for_ajax
+
+ expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(old_issue.title)
+ end
+ end
+ end
+
+ describe 'retains filter when switching issue states' do
+ before do
+ input_filtered_search('bug')
+
+ # Wait for search results to load
+ sleep 2
+ end
+
+ it 'open state' do
+ find('.issues-state-filters a', text: 'Closed').click
+ wait_for_ajax
+
+ find('.issues-state-filters a', text: 'Open').click
+ wait_for_ajax
+
+ expect(page).to have_selector('.issues-list .issue', count: 4)
+ end
+
+ it 'closed state' do
+ find('.issues-state-filters a', text: 'Closed').click
+ wait_for_ajax
+
+ expect(page).to have_selector('.issues-list .issue', count: 1)
+ expect(find('.issues-list .issue:first-of-type .issue-title-text a')).to have_content(closed_issue.title)
+ end
+
+ it 'all state' do
+ find('.issues-state-filters a', text: 'All').click
+ wait_for_ajax
+
+ expect(page).to have_selector('.issues-list .issue', count: 5)
+ end
+ end
+
+ describe 'RSS feeds' do
+ it 'updates atom feed link for project issues' do
+ visit namespace_project_issues_path(project.namespace, project, milestone_title: milestone.title, assignee_id: user.id)
+ link = find('.nav-controls a', text: 'Subscribe')
+ params = CGI.parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
+ auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('milestone_title' => [milestone.title])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
+ expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
+ end
+
+ it 'updates atom feed link for group issues' do
+ visit issues_group_path(group, milestone_title: milestone.title, assignee_id: user.id)
+ link = find('.nav-controls a', text: 'Subscribe')
+ params = CGI.parse(URI.parse(link[:href]).query)
+ auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
+ auto_discovery_params = CGI.parse(URI.parse(auto_discovery_link[:href]).query)
+
+ expect(params).to include('private_token' => [user.private_token])
+ expect(params).to include('milestone_title' => [milestone.title])
+ expect(params).to include('assignee_id' => [user.id.to_s])
+ expect(auto_discovery_params).to include('private_token' => [user.private_token])
+ expect(auto_discovery_params).to include('milestone_title' => [milestone.title])
+ expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
+ end
+ end
+end
diff --git a/spec/features/issues/filtered_search/search_bar_spec.rb b/spec/features/issues/filtered_search/search_bar_spec.rb
new file mode 100644
index 00000000000..56b1d354eb0
--- /dev/null
+++ b/spec/features/issues/filtered_search/search_bar_spec.rb
@@ -0,0 +1,88 @@
+require 'rails_helper'
+
+describe 'Search bar', js: true, feature: true do
+ include WaitForAjax
+
+ let!(:project) { create(:empty_project) }
+ let!(:user) { create(:user) }
+ let(:filtered_search) { find('.filtered-search') }
+
+ before do
+ project.team << [user, :master]
+ login_as(user)
+ create(:issue, project: project)
+
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ def get_left_style(style)
+ left_style = /left:\s\d*[.]\d*px/.match(style)
+ left_style.to_s.gsub('left: ', '').to_f
+ end
+
+ describe 'clear search button' do
+ it 'clears text' do
+ search_text = 'search_text'
+ filtered_search.set(search_text)
+
+ expect(filtered_search.value).to eq(search_text)
+ find('.filtered-search-input-container .clear-search').click
+
+ expect(filtered_search.value).to eq('')
+ end
+
+ it 'hides by default' do
+ expect(page).to have_css('.clear-search', visible: false)
+ end
+
+ it 'hides after clicked' do
+ filtered_search.set('a')
+ find('.filtered-search-input-container .clear-search').click
+
+ expect(page).to have_css('.clear-search', visible: false)
+ end
+
+ it 'hides when there is no text' do
+ filtered_search.set('a')
+ filtered_search.set('')
+
+ expect(page).to have_css('.clear-search', visible: false)
+ end
+
+ it 'shows when there is text' do
+ filtered_search.set('a')
+
+ expect(page).to have_css('.clear-search', visible: true)
+ end
+
+ it 'resets the dropdown hint filter' do
+ filtered_search.click
+ original_size = page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size
+
+ filtered_search.set('author')
+
+ expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(1)
+
+ find('.filtered-search-input-container .clear-search').click
+ filtered_search.click
+
+ expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(original_size)
+ end
+
+ it 'resets the dropdown filters' do
+ filtered_search.set('a')
+ hint_style = page.find('#js-dropdown-hint')['style']
+ hint_offset = get_left_style(hint_style)
+
+ filtered_search.set('author:')
+
+ expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to eq(0)
+
+ find('.filtered-search-input-container .clear-search').click
+ filtered_search.click
+
+ expect(page.all('#js-dropdown-hint .filter-dropdown .filter-dropdown-item').size).to be > 0
+ expect(get_left_style(page.find('#js-dropdown-hint')['style'])).to eq(hint_offset)
+ end
+ end
+end
diff --git a/spec/features/issues/markdown_toolbar_spec.rb b/spec/features/issues/markdown_toolbar_spec.rb
new file mode 100644
index 00000000000..c8c9c50396b
--- /dev/null
+++ b/spec/features/issues/markdown_toolbar_spec.rb
@@ -0,0 +1,37 @@
+require 'rails_helper'
+
+feature 'Issue markdown toolbar', feature: true, js: true do
+ let(:project) { create(:project, :public) }
+ let(:issue) { create(:issue, project: project) }
+ let(:user) { create(:user) }
+
+ before do
+ login_as(user)
+
+ visit namespace_project_issue_path(project.namespace, project, issue)
+ end
+
+ it "doesn't include first new line when adding bold" do
+ find('#note_note').native.send_keys('test')
+ find('#note_note').native.send_key(:enter)
+ find('#note_note').native.send_keys('bold')
+
+ page.evaluate_script('document.querySelectorAll(".js-main-target-form #note_note")[0].setSelectionRange(4, 9)')
+
+ first('.toolbar-btn').click
+
+ expect(find('#note_note')[:value]).to eq("test\n**bold**\n")
+ end
+
+ it "doesn't include first new line when adding underline" do
+ find('#note_note').native.send_keys('test')
+ find('#note_note').native.send_key(:enter)
+ find('#note_note').native.send_keys('underline')
+
+ page.evaluate_script('document.querySelectorAll(".js-main-target-form #note_note")[0].setSelectionRange(4, 50)')
+
+ find('.toolbar-btn:nth-child(2)').click
+
+ expect(find('#note_note')[:value]).to eq("test\n*underline*\n")
+ end
+end
diff --git a/spec/features/issues/reset_filters_spec.rb b/spec/features/issues/reset_filters_spec.rb
deleted file mode 100644
index c9a3ecf16ea..00000000000
--- a/spec/features/issues/reset_filters_spec.rb
+++ /dev/null
@@ -1,89 +0,0 @@
-require 'rails_helper'
-
-feature 'Issues filter reset button', feature: true, js: true do
- include WaitForAjax
- include IssueHelpers
-
- let!(:project) { create(:project, :public) }
- let!(:user) { create(:user)}
- let!(:milestone) { create(:milestone, project: project) }
- let!(:bug) { create(:label, project: project, name: 'bug')}
- let!(:issue1) { create(:issue, project: project, milestone: milestone, author: user, assignee: user, title: 'Feature')}
- let!(:issue2) { create(:labeled_issue, project: project, labels: [bug], title: 'Bugfix1')}
-
- before do
- project.team << [user, :developer]
- end
-
- context 'when a milestone filter has been applied' do
- it 'resets the milestone filter' do
- visit_issues(project, milestone_title: milestone.title)
- expect(page).to have_css('.issue', count: 1)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when a label filter has been applied' do
- it 'resets the label filter' do
- visit_issues(project, label_name: bug.name)
- expect(page).to have_css('.issue', count: 1)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when a text search has been conducted' do
- it 'resets the text search filter' do
- visit_issues(project, search: 'Bug')
- expect(page).to have_css('.issue', count: 1)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when author filter has been applied' do
- it 'resets the author filter' do
- visit_issues(project, author_id: user.id)
- expect(page).to have_css('.issue', count: 1)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when assignee filter has been applied' do
- it 'resets the assignee filter' do
- visit_issues(project, assignee_id: user.id)
- expect(page).to have_css('.issue', count: 1)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when all filters have been applied' do
- it 'resets all filters' do
- visit_issues(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
- expect(page).to have_css('.issue', count: 0)
-
- reset_filters
- expect(page).to have_css('.issue', count: 2)
- end
- end
-
- context 'when no filters have been applied' do
- it 'the reset link should not be visible' do
- visit_issues(project)
- expect(page).to have_css('.issue', count: 2)
- expect(page).not_to have_css '.reset_filters'
- end
- end
-
- def reset_filters
- find('.reset-filters').click
- end
-end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index b071fe480e6..394eb31aff8 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -619,14 +619,18 @@ describe 'Issues', feature: true do
end
it 'adds due date to issue' do
+ date = Date.today.at_beginning_of_month + 2.days
+
page.within '.due_date' do
click_link 'Edit'
page.within '.ui-datepicker-calendar' do
- first('.ui-state-default').click
+ click_link date.day
end
- expect(page).to have_no_content 'None'
+ wait_for_ajax
+
+ expect(find('.value').text).to have_content date.strftime('%b %-d, %Y')
end
end
@@ -638,6 +642,8 @@ describe 'Issues', feature: true do
first('.ui-state-default').click
end
+ wait_for_ajax
+
expect(page).to have_no_content 'No due date'
click_link 'remove due date'
diff --git a/spec/features/merge_requests/deleted_source_branch_spec.rb b/spec/features/merge_requests/deleted_source_branch_spec.rb
index d5c9ed8a3b7..0952b17b63e 100644
--- a/spec/features/merge_requests/deleted_source_branch_spec.rb
+++ b/spec/features/merge_requests/deleted_source_branch_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
# message to be shown by JavaScript when the source branch was deleted.
# Please do not remove "js: true".
describe 'Deleted source branch', feature: true, js: true do
+ include WaitForAjax
+
let(:user) { create(:user) }
let(:merge_request) { create(:merge_request) }
@@ -13,7 +15,8 @@ describe 'Deleted source branch', feature: true, js: true do
merge_request.update!(source_branch: 'this-branch-does-not-exist')
visit namespace_project_merge_request_path(
merge_request.project.namespace,
- merge_request.project, merge_request
+ merge_request.project,
+ merge_request
)
end
@@ -23,11 +26,17 @@ describe 'Deleted source branch', feature: true, js: true do
)
end
- it 'hides Discussion, Commits and Changes tabs' do
+ it 'still contains Discussion, Commits and Changes tabs' do
within '.merge-request-details' do
- expect(page).to have_no_content('Discussion')
- expect(page).to have_no_content('Commits')
- expect(page).to have_no_content('Changes')
+ expect(page).to have_content('Discussion')
+ expect(page).to have_content('Commits')
+ expect(page).to have_content('Changes')
end
+
+ click_on 'Changes'
+ wait_for_ajax
+
+ expect(page).to have_selector('.diffs.tab-pane .nothing-here-block')
+ expect(page).to have_content('Nothing to merge from this-branch-does-not-exist into feature')
end
end
diff --git a/spec/features/issues/filter_by_labels_spec.rb b/spec/features/merge_requests/filter_by_labels_spec.rb
index 0253629f753..4c60329865c 100644
--- a/spec/features/issues/filter_by_labels_spec.rb
+++ b/spec/features/merge_requests/filter_by_labels_spec.rb
@@ -7,25 +7,27 @@ feature 'Issue filtering by Labels', feature: true, js: true do
let!(:user) { create(:user) }
let!(:label) { create(:label, project: project) }
- before do
- bug = create(:label, project: project, title: 'bug')
- feature = create(:label, project: project, title: 'feature')
- enhancement = create(:label, project: project, title: 'enhancement')
+ let!(:bug) { create(:label, project: project, title: 'bug') }
+ let!(:feature) { create(:label, project: project, title: 'feature') }
+ let!(:enhancement) { create(:label, project: project, title: 'enhancement') }
+
+ let!(:mr1) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "bugfix1") }
+ let!(:mr2) { create(:merge_request, title: "Bugfix2", source_project: project, target_project: project, source_branch: "bugfix2") }
+ let!(:mr3) { create(:merge_request, title: "Feature1", source_project: project, target_project: project, source_branch: "feature1") }
- issue1 = create(:issue, title: "Bugfix1", project: project)
- issue1.labels << bug
+ before do
+ mr1.labels << bug
- issue2 = create(:issue, title: "Bugfix2", project: project)
- issue2.labels << bug
- issue2.labels << enhancement
+ mr2.labels << bug
+ mr2.labels << enhancement
- issue3 = create(:issue, title: "Feature1", project: project)
- issue3.labels << feature
+ mr3.title = "Feature1"
+ mr3.labels << feature
project.team << [user, :master]
login_as(user)
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
context 'filter by label bug' do
diff --git a/spec/features/issues/filter_issues_spec.rb b/spec/features/merge_requests/filter_merge_requests_spec.rb
index 0d19563d628..4642b5a530d 100644
--- a/spec/features/issues/filter_issues_spec.rb
+++ b/spec/features/merge_requests/filter_merge_requests_spec.rb
@@ -1,10 +1,10 @@
require 'rails_helper'
-describe 'Filter issues', feature: true do
+describe 'Filter merge requests', feature: true do
include WaitForAjax
+ let!(:project) { create(:project) }
let!(:group) { create(:group) }
- let!(:project) { create(:project, group: group) }
let!(:user) { create(:user)}
let!(:milestone) { create(:milestone, project: project) }
let!(:label) { create(:label, project: project) }
@@ -14,12 +14,12 @@ describe 'Filter issues', feature: true do
project.team << [user, :master]
group.add_developer(user)
login_as(user)
- create(:issue, project: project)
+ create(:merge_request, source_project: project, target_project: project)
end
- describe 'for assignee from issues#index' do
+ describe 'for assignee from mr#index' do
before do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
find('.js-assignee-search').click
@@ -47,9 +47,9 @@ describe 'Filter issues', feature: true do
end
end
- describe 'for milestone from issues#index' do
+ describe 'for milestone from mr#index' do
before do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
find('.js-milestone-select').click
@@ -77,9 +77,9 @@ describe 'Filter issues', feature: true do
end
end
- describe 'for label from issues#index', js: true do
+ describe 'for label from mr#index', js: true do
before do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
find('.js-label-select').click
wait_for_ajax
end
@@ -127,7 +127,7 @@ describe 'Filter issues', feature: true do
expect(page).to have_content wontfix.title
end
- find('.dropdown-menu-close-icon').click
+ find('body').click
expect(find('.filtered-labels')).to have_content(wontfix.title)
@@ -135,7 +135,7 @@ describe 'Filter issues', feature: true do
wait_for_ajax
find('.dropdown-menu-labels a', text: label.title).click
- find('.dropdown-menu-close-icon').click
+ find('body').click
expect(find('.filtered-labels')).to have_content(wontfix.title)
expect(find('.filtered-labels')).to have_content(label.title)
@@ -150,21 +150,21 @@ describe 'Filter issues', feature: true do
it "selects and unselects `won't fix`" do
find('.dropdown-menu-labels a', text: wontfix.title).click
find('.dropdown-menu-labels a', text: wontfix.title).click
-
- find('.dropdown-menu-close-icon').click
+ # Close label dropdown to load
+ find('body').click
expect(page).not_to have_css('.filtered-labels')
end
end
describe 'for assignee and label from issues#index' do
before do
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
find('.js-assignee-search').click
find('.dropdown-menu-user-link', text: user.username).click
- expect(page).not_to have_selector('.issues-list .issue')
+ expect(page).not_to have_selector('.mr-list .merge-request')
find('.js-label-select').click
@@ -196,38 +196,40 @@ describe 'Filter issues', feature: true do
end
end
- describe 'filter issues by text' do
+ describe 'filter merge requests by text' do
before do
- create(:issue, title: "Bug", project: project)
+ create(:merge_request, title: "Bug", source_project: project, target_project: project, source_branch: "bug")
bug_label = create(:label, project: project, title: 'bug')
milestone = create(:milestone, title: "8", project: project)
- issue = create(:issue,
- title: "Bug 2",
- project: project,
+ mr = create(:merge_request,
+ title: "Bug 2",
+ source_project: project,
+ target_project: project,
+ source_branch: "bug2",
milestone: milestone,
author: user,
assignee: user)
- issue.labels << bug_label
+ mr.labels << bug_label
- visit namespace_project_issues_path(project.namespace, project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
context 'only text', js: true do
- it 'filters issues by searched text' do
+ it 'filters merge requests by searched text' do
fill_in 'issuable_search', with: 'Bug'
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
end
- it 'does not show any issues' do
+ it 'does not show any merge requests' do
fill_in 'issuable_search', with: 'testing'
- page.within '.issues-list' do
- expect(page).not_to have_selector('.issue')
+ page.within '.mr-list' do
+ expect(page).not_to have_selector('.merge-request')
end
end
end
@@ -237,8 +239,8 @@ describe 'Filter issues', feature: true do
fill_in 'issuable_search', with: 'Bug'
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
click_button 'Label'
@@ -248,8 +250,8 @@ describe 'Filter issues', feature: true do
find('.dropdown-menu-close-icon').click
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 1)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 1)
end
end
@@ -257,8 +259,8 @@ describe 'Filter issues', feature: true do
fill_in 'issuable_search', with: 'Bug'
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
click_button 'Milestone'
@@ -267,8 +269,8 @@ describe 'Filter issues', feature: true do
end
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 1)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 1)
end
end
@@ -276,8 +278,8 @@ describe 'Filter issues', feature: true do
fill_in 'issuable_search', with: 'Bug'
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
click_button 'Assignee'
@@ -286,8 +288,8 @@ describe 'Filter issues', feature: true do
end
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 1)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 1)
end
end
@@ -295,8 +297,8 @@ describe 'Filter issues', feature: true do
fill_in 'issuable_search', with: 'Bug'
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
click_button 'Author'
@@ -305,26 +307,27 @@ describe 'Filter issues', feature: true do
end
expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 1)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 1)
end
end
end
end
- describe 'filter issues and sort', js: true do
+ describe 'filter merge requests and sort', js: true do
before do
bug_label = create(:label, project: project, title: 'bug')
- bug_one = create(:issue, title: "Frontend", project: project)
- bug_two = create(:issue, title: "Bug 2", project: project)
- bug_one.labels << bug_label
- bug_two.labels << bug_label
+ mr1 = create(:merge_request, title: "Frontend", source_project: project, target_project: project, source_branch: "Frontend")
+ mr2 = create(:merge_request, title: "Bug 2", source_project: project, target_project: project, source_branch: "bug2")
- visit namespace_project_issues_path(project.namespace, project)
+ mr1.labels << bug_label
+ mr2.labels << bug_label
+
+ visit namespace_project_merge_requests_path(project.namespace, project)
end
- it 'is able to filter and sort issues' do
+ it 'is able to filter and sort merge requests' do
click_button 'Label'
wait_for_ajax
page.within '.labels-filter' do
@@ -334,8 +337,8 @@ describe 'Filter issues', feature: true do
wait_for_ajax
expect(page).to have_issuable_counts(open: 2, closed: 0, all: 2)
- page.within '.issues-list' do
- expect(page).to have_selector('.issue', count: 2)
+ page.within '.mr-list' do
+ expect(page).to have_selector('.merge-request', count: 2)
end
click_button 'Last created'
@@ -344,41 +347,9 @@ describe 'Filter issues', feature: true do
end
wait_for_ajax
- page.within '.issues-list' do
+ page.within '.mr-list' do
expect(page).to have_content('Frontend')
end
end
end
-
- it 'updates atom feed link for project issues' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: '', assignee_id: user.id)
-
- link = find('.nav-controls a', text: 'Subscribe')
- params = CGI::parse(URI.parse(link[:href]).query)
- auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
- auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
-
- expect(params).to include('private_token' => [user.private_token])
- expect(params).to include('milestone_title' => [''])
- expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
- expect(auto_discovery_params).to include('milestone_title' => [''])
- expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
- end
-
- it 'updates atom feed link for group issues' do
- visit issues_group_path(group, milestone_title: '', assignee_id: user.id)
-
- link = find('.nav-controls a', text: 'Subscribe')
- params = CGI::parse(URI.parse(link[:href]).query)
- auto_discovery_link = find('link[type="application/atom+xml"]', visible: false)
- auto_discovery_params = CGI::parse(URI.parse(auto_discovery_link[:href]).query)
-
- expect(params).to include('private_token' => [user.private_token])
- expect(params).to include('milestone_title' => [''])
- expect(params).to include('assignee_id' => [user.id.to_s])
- expect(auto_discovery_params).to include('private_token' => [user.private_token])
- expect(auto_discovery_params).to include('milestone_title' => [''])
- expect(auto_discovery_params).to include('assignee_id' => [user.id.to_s])
- end
end
diff --git a/spec/features/merge_requests/reset_filters_spec.rb b/spec/features/merge_requests/reset_filters_spec.rb
new file mode 100644
index 00000000000..3a7ece7e1d6
--- /dev/null
+++ b/spec/features/merge_requests/reset_filters_spec.rb
@@ -0,0 +1,96 @@
+require 'rails_helper'
+
+feature 'Issues filter reset button', feature: true, js: true do
+ include WaitForAjax
+ include IssueHelpers
+
+ let!(:project) { create(:project, :public) }
+ let!(:user) { create(:user)}
+ let!(:milestone) { create(:milestone, project: project) }
+ let!(:bug) { create(:label, project: project, name: 'bug')}
+ let!(:mr1) { create(:merge_request, title: "Feature", source_project: project, target_project: project, source_branch: "Feature", milestone: milestone, author: user, assignee: user) }
+ let!(:mr2) { create(:merge_request, title: "Bugfix1", source_project: project, target_project: project, source_branch: "Bugfix1") }
+
+ let(:merge_request_css) { '.merge-request' }
+
+ before do
+ mr2.labels << bug
+ project.team << [user, :developer]
+ end
+
+ context 'when a milestone filter has been applied' do
+ it 'resets the milestone filter' do
+ visit_merge_requests(project, milestone_title: milestone.title)
+ expect(page).to have_css(merge_request_css, count: 1)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when a label filter has been applied' do
+ it 'resets the label filter' do
+ visit_merge_requests(project, label_name: bug.name)
+ expect(page).to have_css(merge_request_css, count: 1)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when a text search has been conducted' do
+ it 'resets the text search filter' do
+ visit_merge_requests(project, search: 'Bug')
+ expect(page).to have_css(merge_request_css, count: 1)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when author filter has been applied' do
+ it 'resets the author filter' do
+ visit_merge_requests(project, author_id: user.id)
+ expect(page).to have_css(merge_request_css, count: 1)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when assignee filter has been applied' do
+ it 'resets the assignee filter' do
+ visit_merge_requests(project, assignee_id: user.id)
+ expect(page).to have_css(merge_request_css, count: 1)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when all filters have been applied' do
+ it 'resets all filters' do
+ visit_merge_requests(project, assignee_id: user.id, author_id: user.id, milestone_title: milestone.title, label_name: bug.name, search: 'Bug')
+ expect(page).to have_css(merge_request_css, count: 0)
+
+ reset_filters
+ expect(page).to have_css(merge_request_css, count: 2)
+ end
+ end
+
+ context 'when no filters have been applied' do
+ it 'the reset link should not be visible' do
+ visit_merge_requests(project)
+ expect(page).to have_css(merge_request_css, count: 2)
+ expect(page).not_to have_css '.reset_filters'
+ end
+ end
+
+ def visit_merge_requests(project, opts = {})
+ visit namespace_project_merge_requests_path project.namespace, project, opts
+ end
+
+ def reset_filters
+ find('.reset-filters').click
+ end
+end
diff --git a/spec/features/projects/group_links_spec.rb b/spec/features/projects/group_links_spec.rb
index 1a71a03fbd9..8b302a6aa23 100644
--- a/spec/features/projects/group_links_spec.rb
+++ b/spec/features/projects/group_links_spec.rb
@@ -14,10 +14,10 @@ feature 'Project group links', feature: true, js: true do
context 'setting an expiration date for a group link' do
before do
- visit namespace_project_group_links_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
select2 group.id, from: '#link_group_id'
- fill_in 'expires_at', with: (Time.current + 4.5.days).strftime('%Y-%m-%d')
+ fill_in 'expires_at_groups', with: (Time.current + 4.5.days).strftime('%Y-%m-%d')
page.find('body').click
click_on 'Share'
end
diff --git a/spec/features/projects/members/anonymous_user_sees_members_spec.rb b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
index c5e3d143d91..d82cf53c690 100644
--- a/spec/features/projects/members/anonymous_user_sees_members_spec.rb
+++ b/spec/features/projects/members/anonymous_user_sees_members_spec.rb
@@ -11,10 +11,10 @@ feature 'Projects > Members > Anonymous user sees members', feature: true do
end
scenario "anonymous user visits the project's members page and sees the list of members" do
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
expect(current_path).to eq(
- namespace_project_project_members_path(project.namespace, project))
+ namespace_project_settings_members_path(project.namespace, project))
expect(page).to have_content(user.name)
end
end
diff --git a/spec/features/projects/members/group_links_spec.rb b/spec/features/projects/members/group_links_spec.rb
index 94995f7cf95..cffb935ad5a 100644
--- a/spec/features/projects/members/group_links_spec.rb
+++ b/spec/features/projects/members/group_links_spec.rb
@@ -12,7 +12,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
@group_link = create(:project_group_link, project: project, group: group)
login_as(user)
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
end
it 'updates group access level' do
@@ -24,7 +24,7 @@ feature 'Projects > Members > Anonymous user sees members', feature: true, js: t
wait_for_ajax
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
expect(first('.group_member')).to have_content('Guest')
end
diff --git a/spec/features/projects/members/group_members_spec.rb b/spec/features/projects/members/group_members_spec.rb
index 7d0065ee2c4..3385e5972ff 100644
--- a/spec/features/projects/members/group_members_spec.rb
+++ b/spec/features/projects/members/group_members_spec.rb
@@ -19,7 +19,7 @@ feature 'Projects members', feature: true do
context 'with a group invitee' do
before do
group_invitee
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
end
scenario 'does not appear in the project members page' do
@@ -33,7 +33,7 @@ feature 'Projects members', feature: true do
before do
group_invitee
project_invitee
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
end
scenario 'shows the project invitee, the project developer, and the group owner' do
@@ -54,7 +54,7 @@ feature 'Projects members', feature: true do
context 'with a group requester' do
before do
group.request_access(group_requester)
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
end
scenario 'does not appear in the project members page' do
@@ -68,7 +68,7 @@ feature 'Projects members', feature: true do
before do
group.request_access(group_requester)
project.request_access(project_requester)
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
end
scenario 'shows the project requester, the project developer, and the group owner' do
diff --git a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
index b7273021c95..f136d9ce0fa 100644
--- a/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
+++ b/spec/features/projects/members/master_adds_member_with_expiration_date_spec.rb
@@ -14,15 +14,15 @@ feature 'Projects > Members > Master adds member with expiration date', feature:
login_as(master)
end
- scenario 'expiration date is displayed in the members list' do
+ scenario 'expiration date is displayed in the members list', js: true do
travel_to Time.zone.parse('2016-08-06 08:00') do
- visit namespace_project_project_members_path(project.namespace, project)
-
+ visit namespace_project_settings_members_path(project.namespace, project)
page.within '.users-project-form' do
select2(new_member.id, from: '#user_ids', multiple: true)
fill_in 'expires_at', with: '2016-08-10'
- click_on 'Add to project'
end
+ find('.users-project-form').click
+ click_on 'Add to project'
page.within "#project_member_#{new_member.project_members.first.id}" do
expect(page).to have_content('Expires in 4 days')
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 97c42bd7f01..0b4dcaa39c6 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -39,7 +39,7 @@ feature 'Projects > Members > User requests access', feature: true do
open_project_settings_menu
click_link 'Members'
- visit namespace_project_project_members_path(project.namespace, project)
+ visit namespace_project_settings_members_path(project.namespace, project)
page.within('.content') do
expect(page).not_to have_content(user.name)
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index cef50f6f237..ca18ac073d8 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,267 +1,364 @@
require 'spec_helper'
describe 'Pipelines', :feature, :js do
- include GitlabRoutingHelper
- include WaitForAjax
+ include WaitForVueResource
let(:project) { create(:empty_project) }
- let(:user) { create(:user) }
- before do
- login_as(user)
- project.team << [user, :developer]
- end
-
- describe 'GET /:project/pipelines' do
- let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: 'master', status: 'running') }
-
- [:all, :running, :branches].each do |scope|
- context "displaying #{scope}" do
- let(:project) { create(:project) }
-
- before { visit namespace_project_pipelines_path(project.namespace, project, scope: scope) }
-
- it { expect(page).to have_content(pipeline.short_sha) }
- end
- end
-
- context 'anonymous access' do
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ context 'when user is logged in' do
+ let(:user) { create(:user) }
- it { expect(page).to have_http_status(:success) }
+ before do
+ login_as(user)
+ project.team << [user, :developer]
end
- context 'cancelable pipeline' do
- let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
-
- before do
- build.run
- visit namespace_project_pipelines_path(project.namespace, project)
+ describe 'GET /:project/pipelines' do
+ let(:project) { create(:project) }
+
+ let!(:pipeline) do
+ create(
+ :ci_empty_pipeline,
+ project: project,
+ ref: 'master',
+ status: 'running',
+ sha: project.commit.id,
+ )
end
- it { expect(page).to have_link('Cancel') }
- it { expect(page).to have_selector('.ci-running') }
+ [:all, :running, :branches].each do |scope|
+ context "when displaying #{scope}" do
+ before do
+ visit_project_pipelines(scope: scope)
+ end
- context 'when canceling' do
- before { click_link('Cancel') }
-
- it { expect(page).not_to have_link('Cancel') }
- it { expect(page).to have_selector('.ci-canceled') }
+ it 'contains pipeline commit short SHA' do
+ expect(page).to have_content(pipeline.short_sha)
+ end
+ end
end
- end
- context 'retryable pipelines' do
- let!(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', commands: 'test') }
+ context 'when pipeline is cancelable' do
+ let!(:build) do
+ create(:ci_build, pipeline: pipeline,
+ stage: 'test',
+ commands: 'test')
+ end
- before do
- build.drop
- visit namespace_project_pipelines_path(project.namespace, project)
- end
+ before do
+ build.run
+ visit_project_pipelines
+ end
- it { expect(page).to have_link('Retry') }
- it { expect(page).to have_selector('.ci-failed') }
+ it 'indicates that pipeline can be canceled' do
+ expect(page).to have_link('Cancel')
+ expect(page).to have_selector('.ci-running')
+ end
- context 'when retrying' do
- before { click_link('Retry') }
+ context 'when canceling' do
+ before { click_link('Cancel') }
- it { expect(page).not_to have_link('Retry') }
- it { expect(page).to have_selector('.ci-running') }
+ it 'indicated that pipelines was canceled' do
+ expect(page).not_to have_link('Cancel')
+ expect(page).to have_selector('.ci-canceled')
+ end
+ end
end
- end
- context 'with manual actions' do
- let!(:manual) do
- create(:ci_build, :manual, pipeline: pipeline,
- name: 'manual build',
- stage: 'test',
- commands: 'test')
- end
+ context 'when pipeline is retryable' do
+ let!(:build) do
+ create(:ci_build, pipeline: pipeline,
+ stage: 'test',
+ commands: 'test')
+ end
- before do
- visit namespace_project_pipelines_path(project.namespace, project)
- end
+ before do
+ build.drop
+ visit_project_pipelines
+ end
- it 'has link to the manual action' do
- find('.js-pipeline-dropdown-manual-actions').click
+ it 'indicates that pipeline can be retried' do
+ expect(page).to have_link('Retry')
+ expect(page).to have_selector('.ci-failed')
+ end
- expect(page).to have_link('Manual build')
- end
+ context 'when retrying' do
+ before { click_link('Retry') }
- context 'when manual action was played' do
- before do
- find('.js-pipeline-dropdown-manual-actions').click
- click_link('Manual build')
+ it 'shows running pipeline that is not retryable' do
+ expect(page).not_to have_link('Retry')
+ expect(page).to have_selector('.ci-running')
+ end
end
+ end
- it 'enqueues manual action job' do
- expect(manual.reload).to be_pending
+ context 'when pipeline has configuration errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, :invalid, project: project)
end
- end
- end
- context 'for generic statuses' do
- context 'when running' do
- let!(:running) { create(:generic_commit_status, status: 'running', pipeline: pipeline, stage: 'test') }
+ before { visit_project_pipelines }
- before do
- visit namespace_project_pipelines_path(project.namespace, project)
+ it 'contains badge that indicates errors' do
+ expect(page).to have_content 'yaml invalid'
end
- it 'is cancelable' do
- expect(page).to have_link('Cancel')
+ it 'contains badge with tooltip which contains error' do
+ expect(pipeline).to have_yaml_errors
+ expect(page).to have_selector(
+ %Q{span[data-original-title="#{pipeline.yaml_errors}"]})
end
+ end
- it 'has pipeline running' do
- expect(page).to have_selector('.ci-running')
+ context 'with manual actions' do
+ let!(:manual) do
+ create(:ci_build, :manual,
+ pipeline: pipeline,
+ name: 'manual build',
+ stage: 'test',
+ commands: 'test')
end
- context 'when canceling' do
- before { click_link('Cancel') }
+ before { visit_project_pipelines }
- it { expect(page).not_to have_link('Cancel') }
- it { expect(page).to have_selector('.ci-canceled') }
+ it 'has a dropdown with play button' do
+ expect(page).to have_selector('.dropdown-toggle.btn.btn-default .icon-play')
end
- end
- context 'when failed' do
- let!(:status) { create(:generic_commit_status, :pending, pipeline: pipeline, stage: 'test') }
+ it 'has link to the manual action' do
+ find('.js-pipeline-dropdown-manual-actions').click
- before do
- status.drop
- visit namespace_project_pipelines_path(project.namespace, project)
+ expect(page).to have_link('manual build')
end
- it 'is not retryable' do
- expect(page).not_to have_link('Retry')
- end
+ context 'when manual action was played' do
+ before do
+ find('.js-pipeline-dropdown-manual-actions').click
+ click_link('manual build')
+ end
- it 'has failed pipeline' do
- expect(page).to have_selector('.ci-failed')
+ it 'enqueues manual action job' do
+ expect(manual.reload).to be_pending
+ end
end
end
- end
-
- context 'downloadable pipelines' do
- context 'with artifacts' do
- let!(:with_artifacts) { create(:ci_build, :artifacts, :success, pipeline: pipeline, name: 'rspec tests', stage: 'test') }
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ context 'for generic statuses' do
+ context 'when running' do
+ let!(:running) do
+ create(:generic_commit_status,
+ status: 'running',
+ pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before { visit_project_pipelines }
+
+ it 'is cancelable' do
+ expect(page).to have_link('Cancel')
+ end
+
+ it 'has pipeline running' do
+ expect(page).to have_selector('.ci-running')
+ end
+
+ context 'when canceling' do
+ before { click_link('Cancel') }
+
+ it 'indicates that pipeline was canceled' do
+ expect(page).not_to have_link('Cancel')
+ expect(page).to have_selector('.ci-canceled')
+ end
+ end
+ end
- it { expect(page).to have_selector('.build-artifacts') }
- it do
- find('.js-pipeline-dropdown-download').click
- expect(page).to have_link(with_artifacts.name)
+ context 'when failed' do
+ let!(:status) do
+ create(:generic_commit_status, :pending,
+ pipeline: pipeline,
+ stage: 'test')
+ end
+
+ before do
+ status.drop
+ visit_project_pipelines
+ end
+
+ it 'is not retryable' do
+ expect(page).not_to have_link('Retry')
+ end
+
+ it 'has failed pipeline' do
+ expect(page).to have_selector('.ci-failed')
+ end
end
end
- context 'with artifacts expired' do
- let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
+ context 'downloadable pipelines' do
+ context 'with artifacts' do
+ let!(:with_artifacts) do
+ create(:ci_build, :artifacts, :success,
+ pipeline: pipeline,
+ name: 'rspec tests',
+ stage: 'test')
+ end
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ before { visit_project_pipelines }
- it { expect(page).not_to have_selector('.build-artifacts') }
- end
+ it 'has artifats' do
+ expect(page).to have_selector('.build-artifacts')
+ end
- context 'without artifacts' do
- let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') }
+ it 'has artifacts download dropdown' do
+ find('.js-pipeline-dropdown-download').click
- before { visit namespace_project_pipelines_path(project.namespace, project) }
+ expect(page).to have_link(with_artifacts.name)
+ end
+ end
- it { expect(page).not_to have_selector('.build-artifacts') }
- end
- end
+ context 'with artifacts expired' do
+ let!(:with_artifacts_expired) do
+ create(:ci_build, :artifacts_expired, :success,
+ pipeline: pipeline,
+ name: 'rspec',
+ stage: 'test')
+ end
- context 'mini pipleine graph' do
- let!(:build) do
- create(:ci_build, pipeline: pipeline, stage: 'build', name: 'build')
- end
+ before { visit_project_pipelines }
- before do
- visit namespace_project_pipelines_path(project.namespace, project)
- end
+ it { expect(page).not_to have_selector('.build-artifacts') }
+ end
+
+ context 'without artifacts' do
+ let!(:without_artifacts) do
+ create(:ci_build, :success,
+ pipeline: pipeline,
+ name: 'rspec',
+ stage: 'test')
+ end
- it 'should render a mini pipeline graph' do
- endpoint = stage_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline, stage: build.name)
+ before { visit_project_pipelines }
- expect(page).to have_selector('.js-mini-pipeline-graph')
- expect(page).to have_selector(".js-builds-dropdown-button[data-stage-endpoint='#{endpoint}']")
+ it { expect(page).not_to have_selector('.build-artifacts') }
+ end
end
- context 'when clicking a graph stage' do
- it 'should open a dropdown' do
- find('.js-builds-dropdown-button').trigger('click')
+ context 'mini pipeline graph' do
+ let!(:build) do
+ create(:ci_build, :pending, pipeline: pipeline,
+ stage: 'build',
+ name: 'build')
+ end
- wait_for_ajax
+ before { visit_project_pipelines }
- expect(page).to have_link build.name
+ it 'should render a mini pipeline graph' do
+ expect(page).to have_selector('.js-mini-pipeline-graph')
+ expect(page).to have_selector('.js-builds-dropdown-button')
end
- it 'should be possible to retry the failed build' do
- find('.js-builds-dropdown-button').trigger('click')
+ context 'when clicking a stage badge' do
+ it 'should open a dropdown' do
+ find('.js-builds-dropdown-button').trigger('click')
+
+ expect(page).to have_link build.name
+ end
- wait_for_ajax
+ it 'should be possible to cancel pending build' do
+ find('.js-builds-dropdown-button').trigger('click')
+ find('a.js-ci-action-icon').trigger('click')
- find('a.js-ci-action-icon').trigger('click')
- expect(page).not_to have_content('Cancel running')
+ expect(page).to have_content('canceled')
+ expect(build.reload).to be_canceled
+ end
end
end
end
- end
- describe 'POST /:project/pipelines' do
- let(:project) { create(:project) }
+ describe 'POST /:project/pipelines' do
+ let(:project) { create(:project) }
- before { visit new_namespace_project_pipeline_path(project.namespace, project) }
+ before do
+ visit new_namespace_project_pipeline_path(project.namespace, project)
+ end
+
+ context 'for valid commit' do
+ before { fill_in('pipeline[ref]', with: 'master') }
+
+ context 'with gitlab-ci.yml' do
+ before { stub_ci_pipeline_to_return_yaml_file }
- context 'for valid commit' do
- before { fill_in('pipeline[ref]', with: 'master') }
+ it 'creates a new pipeline' do
+ expect { click_on 'Create pipeline' }
+ .to change { Ci::Pipeline.count }.by(1)
+ end
+ end
- context 'with gitlab-ci.yml' do
- before { stub_ci_pipeline_to_return_yaml_file }
+ context 'without gitlab-ci.yml' do
+ before { click_on 'Create pipeline' }
- it { expect{ click_on 'Create pipeline' }.to change{ Ci::Pipeline.count }.by(1) }
+ it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
+ end
end
- context 'without gitlab-ci.yml' do
- before { click_on 'Create pipeline' }
+ context 'for invalid commit' do
+ before do
+ fill_in('pipeline[ref]', with: 'invalid-reference')
+ click_on 'Create pipeline'
+ end
- it { expect(page).to have_content('Missing .gitlab-ci.yml file') }
+ it { expect(page).to have_content('Reference not found') }
end
end
- context 'for invalid commit' do
+ describe 'Create pipelines' do
+ let(:project) { create(:project) }
+
before do
- fill_in('pipeline[ref]', with: 'invalid-reference')
- click_on 'Create pipeline'
+ visit new_namespace_project_pipeline_path(project.namespace, project)
+ end
+
+ describe 'new pipeline page' do
+ it 'has field to add a new pipeline' do
+ expect(page).to have_field('pipeline[ref]')
+ expect(page).to have_content('Create for')
+ end
end
- it { expect(page).to have_content('Reference not found') }
+ describe 'find pipelines' do
+ it 'shows filtered pipelines', js: true do
+ fill_in('pipeline[ref]', with: 'fix')
+ find('input#ref').native.send_keys(:keydown)
+
+ within('.ui-autocomplete') do
+ expect(page).to have_selector('li', text: 'fix')
+ end
+ end
+ end
end
end
- describe 'Create pipelines', feature: true do
- let(:project) { create(:project) }
-
+ context 'when user is not logged in' do
before do
- visit new_namespace_project_pipeline_path(project.namespace, project)
+ visit namespace_project_pipelines_path(project.namespace, project)
end
- describe 'new pipeline page' do
- it 'has field to add a new pipeline' do
- expect(page).to have_field('pipeline[ref]')
- expect(page).to have_content('Create for')
- end
+ context 'when project is public' do
+ let(:project) { create(:project, :public) }
+
+ it { expect(page).to have_content 'No pipelines to show' }
+ it { expect(page).to have_http_status(:success) }
end
- describe 'find pipelines' do
- it 'shows filtered pipelines', js: true do
- fill_in('pipeline[ref]', with: 'fix')
- find('input#ref').native.send_keys(:keydown)
+ context 'when project is private' do
+ let(:project) { create(:project, :private) }
- within('.ui-autocomplete') do
- expect(page).to have_selector('li', text: 'fix')
- end
- end
+ it { expect(page).to have_content 'You need to sign in' }
end
end
+
+ def visit_project_pipelines(**query)
+ visit namespace_project_pipelines_path(project.namespace, project, query)
+ wait_for_vue_resource
+ end
end
diff --git a/spec/features/projects/services/mattermost_slash_command_spec.rb b/spec/features/projects/services/mattermost_slash_command_spec.rb
index 8de827447ff..86a07b2c679 100644
--- a/spec/features/projects/services/mattermost_slash_command_spec.rb
+++ b/spec/features/projects/services/mattermost_slash_command_spec.rb
@@ -33,10 +33,89 @@ feature 'Setup Mattermost slash commands', feature: true do
expect(value).to eq(token)
end
- describe 'mattermost service is enabled' do
- it 'shows the add to mattermost button' do
- expect(page).to have_link 'Add to Mattermost'
+ it 'shows the add to mattermost button' do
+ expect(page).to have_link('Add to Mattermost')
+ end
+
+ it 'shows an explanation if user is a member of no teams' do
+ stub_teams(count: 0)
+
+ click_link 'Add to Mattermost'
+
+ expect(page).to have_content('You aren’t a member of any team on the Mattermost instance')
+ expect(page).to have_link('join a team', href: "#{Gitlab.config.mattermost.host}/select_team")
+ end
+
+ it 'shows an explanation if user is a member of 1 team' do
+ stub_teams(count: 1)
+
+ click_link 'Add to Mattermost'
+
+ expect(page).to have_content('The team where the slash commands will be used in')
+ expect(page).to have_content('This is the only available team.')
+ end
+
+ it 'shows a disabled prefilled select if user is a member of 1 team' do
+ teams = stub_teams(count: 1)
+
+ click_link 'Add to Mattermost'
+
+ team_name = teams.first[1]['display_name']
+ select_element = find('select#mattermost_team_id')
+ selected_option = select_element.find('option[selected]')
+
+ expect(select_element['disabled']).to be(true)
+ expect(selected_option).to have_content(team_name.to_s)
+ end
+
+ it 'has a hidden input for the prefilled value if user is a member of 1 team' do
+ teams = stub_teams(count: 1)
+
+ click_link 'Add to Mattermost'
+
+ expect(find('input#mattermost_team_id', visible: false).value).to eq(teams.first[0].to_s)
+ end
+
+ it 'shows an explanation user is a member of multiple teams' do
+ stub_teams(count: 2)
+
+ click_link 'Add to Mattermost'
+
+ expect(page).to have_content('Select the team where the slash commands will be used in')
+ expect(page).to have_content('The list shows all available teams.')
+ end
+
+ it 'shows a select with team options user is a member of multiple teams' do
+ stub_teams(count: 2)
+
+ click_link 'Add to Mattermost'
+
+ select_element = find('select#mattermost_team_id')
+ selected_option = select_element.find('option[selected]')
+
+ expect(select_element['disabled']).to be(false)
+ expect(selected_option).to have_content('Select team...')
+ # The 'Select team...' placeholder is item `0`.
+ expect(select_element.all('option').count).to eq(3)
+ end
+
+ def stub_teams(count: 0)
+ teams = create_teams(count)
+
+ allow_any_instance_of(MattermostSlashCommandsService).to receive(:list_teams) { teams }
+
+ teams
+ end
+
+ def create_teams(count = 0)
+ teams = {}
+
+ count.times do |i|
+ i += 1
+ teams[i] = { id: i, display_name: i }
end
+
+ teams
end
describe 'mattermost service is not enabled' do
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index caecd027aaa..a05b83959fb 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -169,16 +169,16 @@ describe "Search", feature: true do
find('.dropdown-menu').click_link 'Issues assigned to me'
sleep 2
- expect(page).to have_selector('.issues-holder')
- expect(find('.js-assignee-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(page).to have_selector('.filtered-search')
+ expect(find('.filtered-search').value).to eq("assignee:@#{user.username}")
end
it 'takes user to her issues page when issues authored is clicked' do
find('.dropdown-menu').click_link "Issues I've created"
sleep 2
- expect(page).to have_selector('.issues-holder')
- expect(find('.js-author-search .dropdown-toggle-text')).to have_content(user.name)
+ expect(page).to have_selector('.filtered-search')
+ expect(find('.filtered-search').value).to eq("author:@#{user.username}")
end
it 'takes user to her MR page when MR assigned is clicked' do
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 1897c8119d2..ecebabefff8 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -82,8 +82,8 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for(:visitor) }
end
- describe "GET /:project_path/project_members" do
- subject { namespace_project_project_members_path(project.namespace, project) }
+ describe "GET /:project_path/settings/members" do
+ subject { namespace_project_settings_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index f52e23f9433..9bc59a7c4f9 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -82,8 +82,8 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for(:visitor) }
end
- describe "GET /:project_path/project_members" do
- subject { namespace_project_project_members_path(project.namespace, project) }
+ describe "GET /:project_path/settings/members" do
+ subject { namespace_project_settings_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index bed9e92fcb6..a8d43b3d581 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -82,8 +82,8 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for(:visitor) }
end
- describe "GET /:project_path/project_members" do
- subject { namespace_project_project_members_path(project.namespace, project) }
+ describe "GET /:project_path/settings/members" do
+ subject { namespace_project_settings_members_path(project.namespace, project) }
it { is_expected.to be_allowed_for(:admin) }
it { is_expected.to be_allowed_for(:owner).of(project) }
diff --git a/spec/features/snippets/create_snippet_spec.rb b/spec/features/snippets/create_snippet_spec.rb
index cb95e7828db..5470276bf06 100644
--- a/spec/features/snippets/create_snippet_spec.rb
+++ b/spec/features/snippets/create_snippet_spec.rb
@@ -17,4 +17,18 @@ feature 'Create Snippet', feature: true do
expect(page).to have_content('My Snippet Title')
expect(page).to have_content('Hello World!')
end
+
+ scenario 'Authenticated user creates a snippet with + in filename' do
+ fill_in 'personal_snippet_title', with: 'My Snippet Title'
+ page.within('.file-editor') do
+ find(:xpath, "//input[@id='personal_snippet_file_name']").set 'snippet+file+name'
+ find(:xpath, "//input[@id='personal_snippet_content']").set 'Hello World!'
+ end
+
+ click_button 'Create snippet'
+
+ expect(page).to have_content('My Snippet Title')
+ expect(page).to have_content('snippet+file+name')
+ expect(page).to have_content('Hello World!')
+ end
end
diff --git a/spec/fixtures/config/redis_config_with_env.yml b/spec/fixtures/config/redis_config_with_env.yml
new file mode 100644
index 00000000000..f5860f37e47
--- /dev/null
+++ b/spec/fixtures/config/redis_config_with_env.yml
@@ -0,0 +1,2 @@
+test:
+ url: <%= ENV['TEST_GITLAB_REDIS_URL'] %>
diff --git a/spec/initializers/secret_token_spec.rb b/spec/initializers/secret_token_spec.rb
index 837b0de9a4c..ad7f032d1e5 100644
--- a/spec/initializers/secret_token_spec.rb
+++ b/spec/initializers/secret_token_spec.rb
@@ -2,10 +2,11 @@ require 'spec_helper'
require_relative '../../config/initializers/secret_token'
describe 'create_tokens', lib: true do
+ include StubENV
+
let(:secrets) { ActiveSupport::OrderedOptions.new }
before do
- allow(ENV).to receive(:[]).and_call_original
allow(File).to receive(:write)
allow(File).to receive(:delete)
allow(Rails).to receive_message_chain(:application, :secrets).and_return(secrets)
@@ -17,7 +18,7 @@ describe 'create_tokens', lib: true do
context 'setting secret_key_base and otp_key_base' do
context 'when none of the secrets exist' do
before do
- allow(ENV).to receive(:[]).with('SECRET_KEY_BASE').and_return(nil)
+ stub_env('SECRET_KEY_BASE', nil)
allow(File).to receive(:exist?).with('.secret').and_return(false)
allow(File).to receive(:exist?).with('config/secrets.yml').and_return(false)
allow(self).to receive(:warn_missing_secret)
@@ -69,7 +70,7 @@ describe 'create_tokens', lib: true do
context 'when secret_key_base exists in the environment and secrets.yml' do
before do
- allow(ENV).to receive(:[]).with('SECRET_KEY_BASE').and_return('env_key')
+ stub_env('SECRET_KEY_BASE', 'env_key')
secrets.secret_key_base = 'secret_key_base'
secrets.otp_key_base = 'otp_key_base'
end
diff --git a/spec/javascripts/.eslintrc b/spec/javascripts/.eslintrc
index 7792acffac2..dcbcd014dc3 100644
--- a/spec/javascripts/.eslintrc
+++ b/spec/javascripts/.eslintrc
@@ -1,15 +1,28 @@
{
- "plugins": ["jasmine"],
"env": {
"jasmine": true
},
"extends": "plugin:jasmine/recommended",
+ "globals": {
+ "appendLoadFixtures": false,
+ "appendLoadStyleFixtures": false,
+ "appendSetFixtures": false,
+ "appendSetStyleFixtures": false,
+ "getJSONFixture": false,
+ "loadFixtures": false,
+ "loadJSONFixtures": false,
+ "loadStyleFixtures": false,
+ "preloadFixtures": false,
+ "preloadStyleFixtures": false,
+ "readFixtures": false,
+ "sandbox": false,
+ "setFixtures": false,
+ "setStyleFixtures": false,
+ "spyOnEvent": false
+ },
+ "plugins": ["jasmine"],
"rules": {
"prefer-arrow-callback": 0,
"func-names": 0
- },
- "globals": {
- "fixture": false,
- "spyOnEvent": false
}
}
diff --git a/spec/javascripts/abuse_reports_spec.js.es6 b/spec/javascripts/abuse_reports_spec.js.es6
index 49e56249565..cf19aa05031 100644
--- a/spec/javascripts/abuse_reports_spec.js.es6
+++ b/spec/javascripts/abuse_reports_spec.js.es6
@@ -13,10 +13,10 @@
(index, element) => element.innerText.indexOf(searchText) > -1,
).first();
- fixture.preload(FIXTURE);
+ preloadFixtures(FIXTURE);
beforeEach(function () {
- fixture.load(FIXTURE);
+ loadFixtures(FIXTURE);
this.abuseReports = new global.AbuseReports();
messages = $('.abuse-reports .message');
});
diff --git a/spec/javascripts/activities_spec.js.es6 b/spec/javascripts/activities_spec.js.es6
index 192da4ee8d9..b3617a45bd4 100644
--- a/spec/javascripts/activities_spec.js.es6
+++ b/spec/javascripts/activities_spec.js.es6
@@ -7,7 +7,7 @@
(() => {
window.gon || (window.gon = {});
- const fixtureTemplate = 'event_filter.html';
+ const fixtureTemplate = 'static/event_filter.html.raw';
const filters = [
{
id: 'all',
@@ -35,7 +35,7 @@
describe('Activities', () => {
beforeEach(() => {
- fixture.load(fixtureTemplate);
+ loadFixtures(fixtureTemplate);
new gl.Activities();
});
diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js
index 89201c8cb8b..faba2837d41 100644
--- a/spec/javascripts/awards_handler_spec.js
+++ b/spec/javascripts/awards_handler_spec.js
@@ -34,9 +34,9 @@
};
describe('AwardsHandler', function() {
- fixture.preload('issues/open-issue.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
beforeEach(function() {
- fixture.load('issues/open-issue.html.raw');
+ loadFixtures('issues/open-issue.html.raw');
awardsHandler = new AwardsHandler;
spyOn(awardsHandler, 'postEmoji').and.callFake((function(_this) {
return function(url, emoji, cb) {
diff --git a/spec/javascripts/behaviors/autosize_spec.js b/spec/javascripts/behaviors/autosize_spec.js
index b4573e53a4e..e77d732a32a 100644
--- a/spec/javascripts/behaviors/autosize_spec.js
+++ b/spec/javascripts/behaviors/autosize_spec.js
@@ -6,7 +6,7 @@
describe('Autosize behavior', function() {
var load;
beforeEach(function() {
- return fixture.set('<textarea class="js-autosize" style="resize: vertical"></textarea>');
+ return setFixtures('<textarea class="js-autosize" style="resize: vertical"></textarea>');
});
it('does not overwrite the resize property', function() {
load();
diff --git a/spec/javascripts/behaviors/quick_submit_spec.js b/spec/javascripts/behaviors/quick_submit_spec.js
index 0f61000bc37..1a1f34cfdc0 100644
--- a/spec/javascripts/behaviors/quick_submit_spec.js
+++ b/spec/javascripts/behaviors/quick_submit_spec.js
@@ -5,9 +5,9 @@
(function() {
describe('Quick Submit behavior', function() {
var keydownEvent;
- fixture.preload('behaviors/quick_submit.html');
+ preloadFixtures('static/behaviors/quick_submit.html.raw');
beforeEach(function() {
- fixture.load('behaviors/quick_submit.html');
+ loadFixtures('static/behaviors/quick_submit.html.raw');
$('form').submit(function(e) {
// Prevent a form submit from moving us off the testing page
return e.preventDefault();
diff --git a/spec/javascripts/behaviors/requires_input_spec.js b/spec/javascripts/behaviors/requires_input_spec.js
index c3f4c867d6a..1f62591c06d 100644
--- a/spec/javascripts/behaviors/requires_input_spec.js
+++ b/spec/javascripts/behaviors/requires_input_spec.js
@@ -4,9 +4,9 @@
(function() {
describe('requiresInput', function() {
- fixture.preload('behaviors/requires_input.html');
+ preloadFixtures('static/behaviors/requires_input.html.raw');
beforeEach(function() {
- return fixture.load('behaviors/requires_input.html');
+ return loadFixtures('static/behaviors/requires_input.html.raw');
});
it('disables submit when any field is required', function() {
$('.js-requires-input').requiresInput();
diff --git a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6 b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
index 133712debab..ea953d0f5a5 100644
--- a/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
+++ b/spec/javascripts/bootstrap_linked_tabs_spec.js.es6
@@ -2,10 +2,10 @@
(() => {
describe('Linked Tabs', () => {
- fixture.preload('linked_tabs');
+ preloadFixtures('static/linked_tabs.html.raw');
beforeEach(() => {
- fixture.load('linked_tabs');
+ loadFixtures('static/linked_tabs.html.raw');
});
describe('when is initialized', () => {
diff --git a/spec/javascripts/build_spec.js.es6 b/spec/javascripts/build_spec.js.es6
index 3983cad4c13..0c556382980 100644
--- a/spec/javascripts/build_spec.js.es6
+++ b/spec/javascripts/build_spec.js.es6
@@ -17,10 +17,10 @@ describe('Build', () => {
offset: BUILD_TRACE.length, n_open_tags: 0, fg_color: null, bg_color: null, style_mask: 0,
}));
- fixture.preload('builds/build-with-artifacts.html.raw');
+ preloadFixtures('builds/build-with-artifacts.html.raw');
beforeEach(() => {
- fixture.load('builds/build-with-artifacts.html.raw');
+ loadFixtures('builds/build-with-artifacts.html.raw');
spyOn($, 'ajax');
});
diff --git a/spec/javascripts/dashboard_spec.js.es6 b/spec/javascripts/dashboard_spec.js.es6
index aadf6f518a8..3f6b328348d 100644
--- a/spec/javascripts/dashboard_spec.js.es6
+++ b/spec/javascripts/dashboard_spec.js.es6
@@ -7,7 +7,7 @@
((global) => {
describe('Dashboard', () => {
- const fixtureTemplate = 'dashboard.html';
+ const fixtureTemplate = 'static/dashboard.html.raw';
function todosCountText() {
return $('.js-todos-count').text();
@@ -17,9 +17,9 @@
$(document).trigger('todo:toggle', newCount);
}
- fixture.preload(fixtureTemplate);
+ preloadFixtures(fixtureTemplate);
beforeEach(() => {
- fixture.load(fixtureTemplate);
+ loadFixtures(fixtureTemplate);
new global.Sidebar();
});
diff --git a/spec/javascripts/environments/environment_actions_spec.js.es6 b/spec/javascripts/environments/environment_actions_spec.js.es6
index 4bae3f30bb5..056e4d41e93 100644
--- a/spec/javascripts/environments/environment_actions_spec.js.es6
+++ b/spec/javascripts/environments/environment_actions_spec.js.es6
@@ -2,10 +2,10 @@
//= require environments/components/environment_actions
describe('Actions Component', () => {
- fixture.preload('environments/element.html');
+ preloadFixtures('static/environments/element.html.raw');
beforeEach(() => {
- fixture.load('environments/element.html');
+ loadFixtures('static/environments/element.html.raw');
});
it('should render a dropdown with the provided actions', () => {
diff --git a/spec/javascripts/environments/environment_external_url_spec.js.es6 b/spec/javascripts/environments/environment_external_url_spec.js.es6
index 9f82567c35b..950a5d53fad 100644
--- a/spec/javascripts/environments/environment_external_url_spec.js.es6
+++ b/spec/javascripts/environments/environment_external_url_spec.js.es6
@@ -2,9 +2,9 @@
//= require environments/components/environment_external_url
describe('External URL Component', () => {
- fixture.preload('environments/element.html');
+ preloadFixtures('static/environments/element.html.raw');
beforeEach(() => {
- fixture.load('environments/element.html');
+ loadFixtures('static/environments/element.html.raw');
});
it('should link to the provided externalUrl prop', () => {
diff --git a/spec/javascripts/environments/environment_item_spec.js.es6 b/spec/javascripts/environments/environment_item_spec.js.es6
index 5d7c6b2411d..c178b9cc1ec 100644
--- a/spec/javascripts/environments/environment_item_spec.js.es6
+++ b/spec/javascripts/environments/environment_item_spec.js.es6
@@ -3,9 +3,9 @@
//= require environments/components/environment_item
describe('Environment item', () => {
- fixture.preload('environments/table.html');
+ preloadFixtures('static/environments/table.html.raw');
beforeEach(() => {
- fixture.load('environments/table.html');
+ loadFixtures('static/environments/table.html.raw');
});
describe('When item is folder', () => {
diff --git a/spec/javascripts/environments/environment_rollback_spec.js.es6 b/spec/javascripts/environments/environment_rollback_spec.js.es6
index 77ba0ab38ec..21241116e29 100644
--- a/spec/javascripts/environments/environment_rollback_spec.js.es6
+++ b/spec/javascripts/environments/environment_rollback_spec.js.es6
@@ -1,12 +1,12 @@
//= require vue
//= require environments/components/environment_rollback
describe('Rollback Component', () => {
- fixture.preload('environments/element.html');
+ preloadFixtures('static/environments/element.html.raw');
const retryURL = 'https://gitlab.com/retry';
beforeEach(() => {
- fixture.load('environments/element.html');
+ loadFixtures('static/environments/element.html.raw');
});
it('Should link to the provided retryUrl', () => {
diff --git a/spec/javascripts/environments/environment_stop_spec.js.es6 b/spec/javascripts/environments/environment_stop_spec.js.es6
index 84a41b2bf46..bb998a32f32 100644
--- a/spec/javascripts/environments/environment_stop_spec.js.es6
+++ b/spec/javascripts/environments/environment_stop_spec.js.es6
@@ -1,13 +1,13 @@
//= require vue
//= require environments/components/environment_stop
describe('Stop Component', () => {
- fixture.preload('environments/element.html');
+ preloadFixtures('static/environments/element.html.raw');
let stopURL;
let component;
beforeEach(() => {
- fixture.load('environments/element.html');
+ loadFixtures('static/environments/element.html.raw');
stopURL = '/stop';
component = new window.gl.environmentsList.StopComponent({
diff --git a/spec/javascripts/extensions/jquery_spec.js b/spec/javascripts/extensions/jquery_spec.js
index 76309930f27..91846bb9143 100644
--- a/spec/javascripts/extensions/jquery_spec.js
+++ b/spec/javascripts/extensions/jquery_spec.js
@@ -6,7 +6,7 @@
describe('jQuery extensions', function() {
describe('disable', function() {
beforeEach(function() {
- return fixture.set('<input type="text" />');
+ return setFixtures('<input type="text" />');
});
it('adds the disabled attribute', function() {
var $input;
@@ -23,7 +23,7 @@
});
return describe('enable', function() {
beforeEach(function() {
- return fixture.set('<input type="text" disabled="disabled" class="disabled" />');
+ return setFixtures('<input type="text" disabled="disabled" class="disabled" />');
});
it('removes the disabled attribute', function() {
var $input;
diff --git a/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6 b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
new file mode 100644
index 00000000000..ce61b73aa8a
--- /dev/null
+++ b/spec/javascripts/filtered_search/dropdown_utils_spec.js.es6
@@ -0,0 +1,107 @@
+//= require extensions/array
+//= require filtered_search/dropdown_utils
+//= require filtered_search/filtered_search_tokenizer
+//= require filtered_search/filtered_search_dropdown_manager
+
+(() => {
+ describe('Dropdown Utils', () => {
+ describe('getEscapedText', () => {
+ it('should return same word when it has no space', () => {
+ const escaped = gl.DropdownUtils.getEscapedText('textWithoutSpace');
+ expect(escaped).toBe('textWithoutSpace');
+ });
+
+ it('should escape with double quotes', () => {
+ let escaped = gl.DropdownUtils.getEscapedText('text with space');
+ expect(escaped).toBe('"text with space"');
+
+ escaped = gl.DropdownUtils.getEscapedText('won\'t fix');
+ expect(escaped).toBe('"won\'t fix"');
+ });
+
+ it('should escape with single quotes', () => {
+ const escaped = gl.DropdownUtils.getEscapedText('won"t fix');
+ expect(escaped).toBe('\'won"t fix\'');
+ });
+
+ it('should escape with single quotes by default', () => {
+ const escaped = gl.DropdownUtils.getEscapedText('won"t\' fix');
+ expect(escaped).toBe('\'won"t\' fix\'');
+ });
+ });
+
+ describe('filterWithSymbol', () => {
+ const item = {
+ title: '@root',
+ };
+
+ it('should filter without symbol', () => {
+ const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':roo');
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+
+ it('should filter with symbol', () => {
+ const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':@roo');
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+
+ it('should filter with colon', () => {
+ const updatedItem = gl.DropdownUtils.filterWithSymbol('@', item, ':');
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+ });
+
+ describe('filterHint', () => {
+ it('should filter', () => {
+ let updatedItem = gl.DropdownUtils.filterHint({
+ hint: 'label',
+ }, 'l');
+ expect(updatedItem.droplab_hidden).toBe(false);
+
+ updatedItem = gl.DropdownUtils.filterHint({
+ hint: 'label',
+ }, 'o');
+ expect(updatedItem.droplab_hidden).toBe(true);
+ });
+
+ it('should return droplab_hidden false when item has no hint', () => {
+ const updatedItem = gl.DropdownUtils.filterHint({}, '');
+ expect(updatedItem.droplab_hidden).toBe(false);
+ });
+ });
+
+ describe('setDataValueIfSelected', () => {
+ beforeEach(() => {
+ spyOn(gl.FilteredSearchDropdownManager, 'addWordToInput')
+ .and.callFake(() => {});
+ });
+
+ it('calls addWordToInput when dataValue exists', () => {
+ const selected = {
+ getAttribute: () => 'value',
+ };
+
+ gl.DropdownUtils.setDataValueIfSelected(null, selected);
+ expect(gl.FilteredSearchDropdownManager.addWordToInput.calls.count()).toEqual(1);
+ });
+
+ it('returns true when dataValue exists', () => {
+ const selected = {
+ getAttribute: () => 'value',
+ };
+
+ const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
+ expect(result).toBe(true);
+ });
+
+ it('returns false when dataValue does not exist', () => {
+ const selected = {
+ getAttribute: () => null,
+ };
+
+ const result = gl.DropdownUtils.setDataValueIfSelected(null, selected);
+ expect(result).toBe(false);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
new file mode 100644
index 00000000000..d0d27ceb4a6
--- /dev/null
+++ b/spec/javascripts/filtered_search/filtered_search_dropdown_manager_spec.js.es6
@@ -0,0 +1,59 @@
+//= require extensions/array
+//= require filtered_search/filtered_search_tokenizer
+//= require filtered_search/filtered_search_dropdown_manager
+
+(() => {
+ describe('Filtered Search Dropdown Manager', () => {
+ describe('addWordToInput', () => {
+ function getInputValue() {
+ return document.querySelector('.filtered-search').value;
+ }
+
+ function setInputValue(value) {
+ document.querySelector('.filtered-search').value = value;
+ }
+
+ beforeEach(() => {
+ const input = document.createElement('input');
+ input.classList.add('filtered-search');
+ document.body.appendChild(input);
+ });
+
+ afterEach(() => {
+ document.querySelector('.filtered-search').outerHTML = '';
+ });
+
+ describe('input has no existing value', () => {
+ it('should add just tokenName', () => {
+ gl.FilteredSearchDropdownManager.addWordToInput('milestone');
+ expect(getInputValue()).toBe('milestone:');
+ });
+
+ it('should add tokenName and tokenValue', () => {
+ gl.FilteredSearchDropdownManager.addWordToInput('label', 'none');
+ expect(getInputValue()).toBe('label:none');
+ });
+ });
+
+ describe('input has existing value', () => {
+ it('should be able to just add tokenName', () => {
+ setInputValue('a');
+ gl.FilteredSearchDropdownManager.addWordToInput('author');
+ expect(getInputValue()).toBe('author:');
+ });
+
+ it('should replace tokenValue', () => {
+ setInputValue('author:roo');
+ gl.FilteredSearchDropdownManager.addWordToInput('author', '@root');
+ expect(getInputValue()).toBe('author:@root');
+ });
+
+ it('should add tokenValues containing spaces', () => {
+ setInputValue('label:~"test');
+ gl.FilteredSearchDropdownManager.addWordToInput('label', '~\'"test me"\'');
+ expect(getInputValue()).toBe('label:~\'"test me"\'');
+ });
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
new file mode 100644
index 00000000000..6df7c0e44ef
--- /dev/null
+++ b/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js.es6
@@ -0,0 +1,104 @@
+//= require extensions/array
+//= require filtered_search/filtered_search_token_keys
+
+(() => {
+ describe('Filtered Search Token Keys', () => {
+ describe('get', () => {
+ let tokenKeys;
+
+ beforeEach(() => {
+ tokenKeys = gl.FilteredSearchTokenKeys.get();
+ });
+
+ it('should return tokenKeys', () => {
+ expect(tokenKeys !== null).toBe(true);
+ });
+
+ it('should return tokenKeys as an array', () => {
+ expect(tokenKeys instanceof Array).toBe(true);
+ });
+ });
+
+ describe('getConditions', () => {
+ let conditions;
+
+ beforeEach(() => {
+ conditions = gl.FilteredSearchTokenKeys.getConditions();
+ });
+
+ it('should return conditions', () => {
+ expect(conditions !== null).toBe(true);
+ });
+
+ it('should return conditions as an array', () => {
+ expect(conditions instanceof Array).toBe(true);
+ });
+ });
+
+ describe('searchByKey', () => {
+ it('should return null when key not found', () => {
+ const tokenKey = gl.FilteredSearchTokenKeys.searchByKey('notakey');
+ expect(tokenKey === null).toBe(true);
+ });
+
+ it('should return tokenKey when found by key', () => {
+ const tokenKeys = gl.FilteredSearchTokenKeys.get();
+ const result = gl.FilteredSearchTokenKeys.searchByKey(tokenKeys[0].key);
+ expect(result).toEqual(tokenKeys[0]);
+ });
+ });
+
+ describe('searchBySymbol', () => {
+ it('should return null when symbol not found', () => {
+ const tokenKey = gl.FilteredSearchTokenKeys.searchBySymbol('notasymbol');
+ expect(tokenKey === null).toBe(true);
+ });
+
+ it('should return tokenKey when found by symbol', () => {
+ const tokenKeys = gl.FilteredSearchTokenKeys.get();
+ const result = gl.FilteredSearchTokenKeys.searchBySymbol(tokenKeys[0].symbol);
+ expect(result).toEqual(tokenKeys[0]);
+ });
+ });
+
+ describe('searchByKeyParam', () => {
+ it('should return null when key param not found', () => {
+ const tokenKey = gl.FilteredSearchTokenKeys.searchByKeyParam('notakeyparam');
+ expect(tokenKey === null).toBe(true);
+ });
+
+ it('should return tokenKey when found by key param', () => {
+ const tokenKeys = gl.FilteredSearchTokenKeys.get();
+ const result = gl.FilteredSearchTokenKeys.searchByKeyParam(`${tokenKeys[0].key}_${tokenKeys[0].param}`);
+ expect(result).toEqual(tokenKeys[0]);
+ });
+ });
+
+ describe('searchByConditionUrl', () => {
+ it('should return null when condition url not found', () => {
+ const condition = gl.FilteredSearchTokenKeys.searchByConditionUrl(null);
+ expect(condition === null).toBe(true);
+ });
+
+ it('should return condition when found by url', () => {
+ const conditions = gl.FilteredSearchTokenKeys.getConditions();
+ const result = gl.FilteredSearchTokenKeys.searchByConditionUrl(conditions[0].url);
+ expect(result).toBe(conditions[0]);
+ });
+ });
+
+ describe('searchByConditionKeyValue', () => {
+ it('should return null when condition tokenKey and value not found', () => {
+ const condition = gl.FilteredSearchTokenKeys.searchByConditionKeyValue(null, null);
+ expect(condition === null).toBe(true);
+ });
+
+ it('should return condition when found by tokenKey and value', () => {
+ const conditions = gl.FilteredSearchTokenKeys.getConditions();
+ const result = gl.FilteredSearchTokenKeys
+ .searchByConditionKeyValue(conditions[0].tokenKey, conditions[0].value);
+ expect(result).toEqual(conditions[0]);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6 b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
new file mode 100644
index 00000000000..ac7f8e9cbcd
--- /dev/null
+++ b/spec/javascripts/filtered_search/filtered_search_tokenizer_spec.js.es6
@@ -0,0 +1,104 @@
+//= require extensions/array
+//= require filtered_search/filtered_search_token_keys
+//= require filtered_search/filtered_search_tokenizer
+
+(() => {
+ describe('Filtered Search Tokenizer', () => {
+ describe('processTokens', () => {
+ it('returns for input containing only search value', () => {
+ const results = gl.FilteredSearchTokenizer.processTokens('searchTerm');
+ expect(results.searchToken).toBe('searchTerm');
+ expect(results.tokens.length).toBe(0);
+ expect(results.lastToken).toBe(results.searchToken);
+ });
+
+ it('returns for input containing only tokens', () => {
+ const results = gl.FilteredSearchTokenizer
+ .processTokens('author:@root label:~"Very Important" milestone:%v1.0 assignee:none');
+ expect(results.searchToken).toBe('');
+ expect(results.tokens.length).toBe(4);
+ expect(results.tokens[3]).toBe(results.lastToken);
+
+ expect(results.tokens[0].key).toBe('author');
+ expect(results.tokens[0].value).toBe('root');
+ expect(results.tokens[0].symbol).toBe('@');
+
+ expect(results.tokens[1].key).toBe('label');
+ expect(results.tokens[1].value).toBe('"Very Important"');
+ expect(results.tokens[1].symbol).toBe('~');
+
+ expect(results.tokens[2].key).toBe('milestone');
+ expect(results.tokens[2].value).toBe('v1.0');
+ expect(results.tokens[2].symbol).toBe('%');
+
+ expect(results.tokens[3].key).toBe('assignee');
+ expect(results.tokens[3].value).toBe('none');
+ expect(results.tokens[3].symbol).toBe('');
+ });
+
+ it('returns for input starting with search value and ending with tokens', () => {
+ const results = gl.FilteredSearchTokenizer
+ .processTokens('searchTerm anotherSearchTerm milestone:none');
+ expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
+ expect(results.tokens.length).toBe(1);
+ expect(results.tokens[0]).toBe(results.lastToken);
+ expect(results.tokens[0].key).toBe('milestone');
+ expect(results.tokens[0].value).toBe('none');
+ expect(results.tokens[0].symbol).toBe('');
+ });
+
+ it('returns for input starting with tokens and ending with search value', () => {
+ const results = gl.FilteredSearchTokenizer
+ .processTokens('assignee:@user searchTerm');
+
+ expect(results.searchToken).toBe('searchTerm');
+ expect(results.tokens.length).toBe(1);
+ expect(results.tokens[0].key).toBe('assignee');
+ expect(results.tokens[0].value).toBe('user');
+ expect(results.tokens[0].symbol).toBe('@');
+ expect(results.lastToken).toBe(results.searchToken);
+ });
+
+ it('returns for input containing search value wrapped between tokens', () => {
+ const results = gl.FilteredSearchTokenizer
+ .processTokens('author:@root label:~"Won\'t fix" searchTerm anotherSearchTerm milestone:none');
+
+ expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
+ expect(results.tokens.length).toBe(3);
+ expect(results.tokens[2]).toBe(results.lastToken);
+
+ expect(results.tokens[0].key).toBe('author');
+ expect(results.tokens[0].value).toBe('root');
+ expect(results.tokens[0].symbol).toBe('@');
+
+ expect(results.tokens[1].key).toBe('label');
+ expect(results.tokens[1].value).toBe('"Won\'t fix"');
+ expect(results.tokens[1].symbol).toBe('~');
+
+ expect(results.tokens[2].key).toBe('milestone');
+ expect(results.tokens[2].value).toBe('none');
+ expect(results.tokens[2].symbol).toBe('');
+ });
+
+ it('returns for input containing search value in between tokens', () => {
+ const results = gl.FilteredSearchTokenizer
+ .processTokens('author:@root searchTerm assignee:none anotherSearchTerm label:~Doing');
+ expect(results.searchToken).toBe('searchTerm anotherSearchTerm');
+ expect(results.tokens.length).toBe(3);
+ expect(results.tokens[2]).toBe(results.lastToken);
+
+ expect(results.tokens[0].key).toBe('author');
+ expect(results.tokens[0].value).toBe('root');
+ expect(results.tokens[0].symbol).toBe('@');
+
+ expect(results.tokens[1].key).toBe('assignee');
+ expect(results.tokens[1].value).toBe('none');
+ expect(results.tokens[1].symbol).toBe('');
+
+ expect(results.tokens[2].key).toBe('label');
+ expect(results.tokens[2].value).toBe('Doing');
+ expect(results.tokens[2].symbol).toBe('~');
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/fixtures/projects.json b/spec/javascripts/fixtures/projects.json
index 4919d77e5a4..4ce7f5c601a 100644
--- a/spec/javascripts/fixtures/projects.json
+++ b/spec/javascripts/fixtures/projects.json
@@ -1 +1,445 @@
-[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":true,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"only_allow_merge_if_build_succeeds":false,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
+[{
+ "id": 9,
+ "description": "",
+ "default_branch": null,
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "phil@localhost:root/test.git",
+ "http_url_to_repo": "http://localhost:3000/root/test.git",
+ "web_url": "http://localhost:3000/root/test",
+ "owner": {
+ "name": "Administrator",
+ "username": "root",
+ "id": 1,
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon",
+ "web_url": "http://localhost:3000/u/root"
+ },
+ "name": "test",
+ "name_with_namespace": "Administrator / test",
+ "path": "test",
+ "path_with_namespace": "root/test",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-14T19:08:05.364Z",
+ "last_activity_at": "2016-01-14T19:08:07.418Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 1,
+ "name": "root",
+ "path": "root",
+ "owner_id": 1,
+ "created_at": "2016-01-13T20:19:44.439Z",
+ "updated_at": "2016-01-13T20:19:44.439Z",
+ "description": "",
+ "avatar": null
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 0,
+ "permissions": {
+ "project_access": null,
+ "group_access": null
+ }
+}, {
+ "id": 8,
+ "description": "Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "phil@localhost:h5bp/html5-boilerplate.git",
+ "http_url_to_repo": "http://localhost:3000/h5bp/html5-boilerplate.git",
+ "web_url": "http://localhost:3000/h5bp/html5-boilerplate",
+ "name": "Html5 Boilerplate",
+ "name_with_namespace": "H5bp / Html5 Boilerplate",
+ "path": "html5-boilerplate",
+ "path_with_namespace": "h5bp/html5-boilerplate",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:57.525Z",
+ "last_activity_at": "2016-01-13T20:27:57.280Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 5,
+ "name": "H5bp",
+ "path": "h5bp",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:57.239Z",
+ "updated_at": "2016-01-13T20:19:57.239Z",
+ "description": "Tempore accusantium possimus aut libero.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 5,
+ "permissions": {
+ "project_access": {
+ "access_level": 10,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 7,
+ "description": "Modi odio mollitia dolorem qui.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "phil@localhost:twitter/typeahead-js.git",
+ "http_url_to_repo": "http://localhost:3000/twitter/typeahead-js.git",
+ "web_url": "http://localhost:3000/twitter/typeahead-js",
+ "name": "Typeahead.Js",
+ "name_with_namespace": "Twitter / Typeahead.Js",
+ "path": "typeahead-js",
+ "path_with_namespace": "twitter/typeahead-js",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:56.212Z",
+ "last_activity_at": "2016-01-13T20:27:51.496Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:54.480Z",
+ "updated_at": "2016-01-13T20:19:54.480Z",
+ "description": "Id voluptatem ipsa maiores omnis repudiandae et et.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": true,
+ "open_issues_count": 4,
+ "permissions": {
+ "project_access": null,
+ "group_access": {
+ "access_level": 10,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 6,
+ "description": "Omnis asperiores ipsa et beatae quidem necessitatibus quia.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "phil@localhost:twitter/flight.git",
+ "http_url_to_repo": "http://localhost:3000/twitter/flight.git",
+ "web_url": "http://localhost:3000/twitter/flight",
+ "name": "Flight",
+ "name_with_namespace": "Twitter / Flight",
+ "path": "flight",
+ "path_with_namespace": "twitter/flight",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:54.754Z",
+ "last_activity_at": "2016-01-13T20:27:50.502Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 4,
+ "name": "Twitter",
+ "path": "twitter",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:54.480Z",
+ "updated_at": "2016-01-13T20:19:54.480Z",
+ "description": "Id voluptatem ipsa maiores omnis repudiandae et et.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": true,
+ "open_issues_count": 4,
+ "permissions": {
+ "project_access": null,
+ "group_access": {
+ "access_level": 10,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 5,
+ "description": "Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-test.git",
+ "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-test.git",
+ "web_url": "http://localhost:3000/gitlab-org/gitlab-test",
+ "name": "Gitlab Test",
+ "name_with_namespace": "Gitlab Org / Gitlab Test",
+ "path": "gitlab-test",
+ "path_with_namespace": "gitlab-org/gitlab-test",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:53.202Z",
+ "last_activity_at": "2016-01-13T20:27:41.626Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 3,
+ "name": "Gitlab Org",
+ "path": "gitlab-org",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:48.851Z",
+ "updated_at": "2016-01-13T20:19:48.851Z",
+ "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 5,
+ "permissions": {
+ "project_access": null,
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 4,
+ "description": "Aut molestias quas est ut aperiam officia quod libero.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-shell.git",
+ "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-shell.git",
+ "web_url": "http://localhost:3000/gitlab-org/gitlab-shell",
+ "name": "Gitlab Shell",
+ "name_with_namespace": "Gitlab Org / Gitlab Shell",
+ "path": "gitlab-shell",
+ "path_with_namespace": "gitlab-org/gitlab-shell",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:51.882Z",
+ "last_activity_at": "2016-01-13T20:27:35.678Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 3,
+ "name": "Gitlab Org",
+ "path": "gitlab-org",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:48.851Z",
+ "updated_at": "2016-01-13T20:19:48.851Z",
+ "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 5,
+ "permissions": {
+ "project_access": {
+ "access_level": 20,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 3,
+ "description": "Excepturi molestiae quia repellendus omnis est illo illum eligendi.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": true,
+ "archived": false,
+ "visibility_level": 20,
+ "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ci.git",
+ "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ci.git",
+ "web_url": "http://localhost:3000/gitlab-org/gitlab-ci",
+ "name": "Gitlab Ci",
+ "name_with_namespace": "Gitlab Org / Gitlab Ci",
+ "path": "gitlab-ci",
+ "path_with_namespace": "gitlab-org/gitlab-ci",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:50.346Z",
+ "last_activity_at": "2016-01-13T20:27:30.115Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 3,
+ "name": "Gitlab Org",
+ "path": "gitlab-org",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:48.851Z",
+ "updated_at": "2016-01-13T20:19:48.851Z",
+ "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 3,
+ "permissions": {
+ "project_access": null,
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 2,
+ "description": "Adipisci quaerat dignissimos enim sed ipsam dolorem quia.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 10,
+ "ssh_url_to_repo": "phil@localhost:gitlab-org/gitlab-ce.git",
+ "http_url_to_repo": "http://localhost:3000/gitlab-org/gitlab-ce.git",
+ "web_url": "http://localhost:3000/gitlab-org/gitlab-ce",
+ "name": "Gitlab Ce",
+ "name_with_namespace": "Gitlab Org / Gitlab Ce",
+ "path": "gitlab-ce",
+ "path_with_namespace": "gitlab-org/gitlab-ce",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:49.065Z",
+ "last_activity_at": "2016-01-13T20:26:58.454Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 3,
+ "name": "Gitlab Org",
+ "path": "gitlab-org",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:48.851Z",
+ "updated_at": "2016-01-13T20:19:48.851Z",
+ "description": "Magni mollitia quod quidem soluta nesciunt impedit.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 5,
+ "permissions": {
+ "project_access": {
+ "access_level": 30,
+ "notification_level": 3
+ },
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}, {
+ "id": 1,
+ "description": "Vel voluptatem maxime saepe ex quia.",
+ "default_branch": "master",
+ "tag_list": [],
+ "public": false,
+ "archived": false,
+ "visibility_level": 0,
+ "ssh_url_to_repo": "phil@localhost:documentcloud/underscore.git",
+ "http_url_to_repo": "http://localhost:3000/documentcloud/underscore.git",
+ "web_url": "http://localhost:3000/documentcloud/underscore",
+ "name": "Underscore",
+ "name_with_namespace": "Documentcloud / Underscore",
+ "path": "underscore",
+ "path_with_namespace": "documentcloud/underscore",
+ "issues_enabled": true,
+ "merge_requests_enabled": true,
+ "wiki_enabled": true,
+ "builds_enabled": true,
+ "snippets_enabled": false,
+ "created_at": "2016-01-13T20:19:45.862Z",
+ "last_activity_at": "2016-01-13T20:25:03.106Z",
+ "shared_runners_enabled": true,
+ "creator_id": 1,
+ "namespace": {
+ "id": 2,
+ "name": "Documentcloud",
+ "path": "documentcloud",
+ "owner_id": null,
+ "created_at": "2016-01-13T20:19:44.464Z",
+ "updated_at": "2016-01-13T20:19:44.464Z",
+ "description": "Aut impedit perferendis fuga et ipsa repellat cupiditate et.",
+ "avatar": {
+ "url": null
+ }
+ },
+ "avatar_url": null,
+ "star_count": 0,
+ "forks_count": 0,
+ "only_allow_merge_if_build_succeeds": false,
+ "open_issues_count": 5,
+ "permissions": {
+ "project_access": null,
+ "group_access": {
+ "access_level": 50,
+ "notification_level": 3
+ }
+ }
+}]
diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb
new file mode 100644
index 00000000000..4569f16f0ca
--- /dev/null
+++ b/spec/javascripts/fixtures/static_fixtures.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+describe ApplicationController, '(Static JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ before(:all) do
+ clean_frontend_fixtures('static/')
+ end
+
+ fixtures_path = File.expand_path(JavaScriptFixturesHelpers::FIXTURE_PATH, Rails.root)
+ haml_fixtures = Dir.glob(File.expand_path('**/*.haml', fixtures_path)).map do |file_path|
+ file_path.sub(/\A#{fixtures_path}#{File::SEPARATOR}/, '')
+ end
+
+ haml_fixtures.each do |template_file_name|
+ it "static/#{template_file_name.sub(/\.haml\z/, '.raw')}" do |example|
+ fixture_file_name = example.description
+ rendered = render_template(template_file_name)
+ store_frontend_fixture(rendered, fixture_file_name)
+ end
+ end
+
+ private
+
+ def render_template(template_file_name)
+ fixture_path = JavaScriptFixturesHelpers::FIXTURE_PATH
+ controller = ApplicationController.new
+ controller.prepend_view_path(fixture_path)
+ controller.render_to_string(template: template_file_name, layout: false)
+ end
+end
diff --git a/spec/javascripts/fixtures/u2f.rb b/spec/javascripts/fixtures/u2f.rb
new file mode 100644
index 00000000000..c9c0b891237
--- /dev/null
+++ b/spec/javascripts/fixtures/u2f.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+context 'U2F' do
+ include JavaScriptFixturesHelpers
+
+ let(:user) { create(:user, :two_factor_via_u2f) }
+
+ before(:all) do
+ clean_frontend_fixtures('u2f/')
+ end
+
+ describe SessionsController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ @request.env['devise.mapping'] = Devise.mappings[:user]
+ end
+
+ it 'u2f/authenticate.html.raw' do |example|
+ allow(controller).to receive(:find_user).and_return(user)
+
+ post :create, user: { login: user.username, password: user.password }
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+
+ describe Profiles::TwoFactorAuthsController, '(JavaScript fixtures)', type: :controller do
+ render_views
+
+ before do
+ sign_in(user)
+ end
+
+ it 'u2f/register.html.raw' do |example|
+ get :show
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+ end
+end
diff --git a/spec/javascripts/fixtures/u2f/authenticate.html.haml b/spec/javascripts/fixtures/u2f/authenticate.html.haml
deleted file mode 100644
index 779d6429a5f..00000000000
--- a/spec/javascripts/fixtures/u2f/authenticate.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-= render partial: "u2f/authenticate", locals: { new_user_session_path: "/users/sign_in", params: {}, resource_name: "user" }
diff --git a/spec/javascripts/fixtures/u2f/register.html.haml b/spec/javascripts/fixtures/u2f/register.html.haml
deleted file mode 100644
index 5ed51be689c..00000000000
--- a/spec/javascripts/fixtures/u2f/register.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-- user = FactoryGirl.build(:user, :two_factor_via_otp)
-= render partial: "u2f/register", locals: { create_u2f_profile_two_factor_auth_path: '/profile/two_factor_auth/create_u2f', current_user: user }
diff --git a/spec/javascripts/gl_dropdown_spec.js.es6 b/spec/javascripts/gl_dropdown_spec.js.es6
index bfaf90e2aee..ce96571bd52 100644
--- a/spec/javascripts/gl_dropdown_spec.js.es6
+++ b/spec/javascripts/gl_dropdown_spec.js.es6
@@ -43,8 +43,7 @@
}
describe('Dropdown', function describeDropdown() {
- fixture.preload('gl_dropdown.html');
- fixture.preload('projects.json');
+ preloadFixtures('static/gl_dropdown.html.raw');
function initDropDown(hasRemote, isFilterable) {
this.dropdownButtonElement = $('#js-project-dropdown', this.dropdownContainerElement).glDropdown({
@@ -61,10 +60,10 @@
}
beforeEach(() => {
- fixture.load('gl_dropdown.html');
+ loadFixtures('static/gl_dropdown.html.raw');
this.dropdownContainerElement = $('.dropdown.inline');
this.$dropdownMenuElement = $('.dropdown-menu', this.dropdownContainerElement);
- this.projectsData = fixture.load('projects.json')[0];
+ this.projectsData = getJSONFixture('projects.json');
});
afterEach(() => {
diff --git a/spec/javascripts/gl_field_errors_spec.js.es6 b/spec/javascripts/gl_field_errors_spec.js.es6
index 5018e87ad6c..e5d934540af 100644
--- a/spec/javascripts/gl_field_errors_spec.js.es6
+++ b/spec/javascripts/gl_field_errors_spec.js.es6
@@ -4,11 +4,11 @@
//= require gl_field_errors
((global) => {
- fixture.preload('gl_field_errors.html');
+ preloadFixtures('static/gl_field_errors.html.raw');
describe('GL Style Field Errors', function() {
beforeEach(function() {
- fixture.load('gl_field_errors.html');
+ loadFixtures('static/gl_field_errors.html.raw');
const $form = this.$form = $('form.gl-show-field-errors');
this.fieldErrors = new global.GlFieldErrors($form);
});
diff --git a/spec/javascripts/header_spec.js b/spec/javascripts/header_spec.js
index d2bcbc37b64..b5262afa1cf 100644
--- a/spec/javascripts/header_spec.js
+++ b/spec/javascripts/header_spec.js
@@ -7,7 +7,7 @@
describe('Header', function() {
var todosPendingCount = '.todos-pending-count';
- var fixtureTemplate = 'header.html';
+ var fixtureTemplate = 'static/header.html.raw';
function isTodosCountHidden() {
return $(todosPendingCount).hasClass('hidden');
@@ -17,9 +17,9 @@
$(document).trigger('todo:toggle', newCount);
}
- fixture.preload(fixtureTemplate);
+ preloadFixtures(fixtureTemplate);
beforeEach(function() {
- fixture.load(fixtureTemplate);
+ loadFixtures(fixtureTemplate);
});
it('should update todos-pending-count after receiving the todo:toggle event', function() {
diff --git a/spec/javascripts/issuable_spec.js.es6 b/spec/javascripts/issuable_spec.js.es6
index d61601ee4fb..917a6267b92 100644
--- a/spec/javascripts/issuable_spec.js.es6
+++ b/spec/javascripts/issuable_spec.js.es6
@@ -21,10 +21,10 @@
}
describe('Issuable', () => {
- fixture.preload('issuable_filter');
+ preloadFixtures('static/issuable_filter.html.raw');
beforeEach(() => {
- fixture.load('issuable_filter');
+ loadFixtures('static/issuable_filter.html.raw');
Issuable.init();
});
@@ -37,7 +37,7 @@
beforeEach(() => {
$filtersForm = $('.js-filter-form');
- fixture.load('issuable_filter');
+ loadFixtures('static/issuable_filter.html.raw');
resetForm($filtersForm);
});
diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js
index faab5ae00c2..eb07421826c 100644
--- a/spec/javascripts/issue_spec.js
+++ b/spec/javascripts/issue_spec.js
@@ -8,9 +8,9 @@
var INVALID_URL = 'http://goesnowhere.nothing/whereami';
var $boxClosed, $boxOpen, $btnClose, $btnReopen;
- fixture.preload('issues/closed-issue.html');
- fixture.preload('issues/issue-with-task-list.html');
- fixture.preload('issues/open-issue.html');
+ preloadFixtures('issues/closed-issue.html.raw');
+ preloadFixtures('issues/issue-with-task-list.html.raw');
+ preloadFixtures('issues/open-issue.html.raw');
function expectErrorMessage() {
var $flashMessage = $('div.flash-alert');
@@ -61,8 +61,8 @@
describe('Issue', function() {
describe('task lists', function() {
- fixture.load('issues/issue-with-task-list.html');
beforeEach(function() {
+ loadFixtures('issues/issue-with-task-list.html.raw');
this.issue = new Issue();
});
@@ -86,7 +86,7 @@
describe('close issue', function() {
beforeEach(function() {
- fixture.load('issues/open-issue.html');
+ loadFixtures('issues/open-issue.html.raw');
findElements();
this.issue = new Issue();
@@ -140,7 +140,7 @@
describe('reopen issue', function() {
beforeEach(function() {
- fixture.load('issues/closed-issue.html');
+ loadFixtures('issues/closed-issue.html.raw');
findElements();
this.issue = new Issue();
diff --git a/spec/javascripts/labels_issue_sidebar_spec.js.es6 b/spec/javascripts/labels_issue_sidebar_spec.js.es6
index 0c48d04776f..e3146559a4a 100644
--- a/spec/javascripts/labels_issue_sidebar_spec.js.es6
+++ b/spec/javascripts/labels_issue_sidebar_spec.js.es6
@@ -17,10 +17,10 @@
(() => {
let saveLabelCount = 0;
describe('Issue dropdown sidebar', () => {
- fixture.preload('issue_sidebar_label.html');
+ preloadFixtures('static/issue_sidebar_label.html.raw');
beforeEach(() => {
- fixture.load('issue_sidebar_label.html');
+ loadFixtures('static/issue_sidebar_label.html.raw');
new IssuableContext('{"id":1,"name":"Administrator","username":"root"}');
new LabelsSelect();
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js.es6 b/spec/javascripts/lib/utils/common_utils_spec.js.es6
index ef75f600898..031f9ca03c9 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js.es6
+++ b/spec/javascripts/lib/utils/common_utils_spec.js.es6
@@ -15,6 +15,7 @@
expect(gl.utils.parseUrl('" test="asf"').pathname).toEqual('/teaspoon/%22%20test=%22asf%22');
});
});
+
describe('gl.utils.parseUrlPathname', () => {
beforeEach(() => {
spyOn(gl.utils, 'parseUrl').and.callFake(url => ({
@@ -28,5 +29,28 @@
expect(gl.utils.parseUrlPathname('some/relative/url')).toEqual('/some/relative/url');
});
});
+
+ describe('gl.utils.getUrlParamsArray', () => {
+ it('should return params array', () => {
+ expect(gl.utils.getUrlParamsArray() instanceof Array).toBe(true);
+ });
+
+ it('should remove the question mark from the search params', () => {
+ const paramsArray = gl.utils.getUrlParamsArray();
+ expect(paramsArray[0][0] !== '?').toBe(true);
+ });
+ });
+
+ describe('gl.utils.getParameterByName', () => {
+ it('should return valid parameter', () => {
+ const value = gl.utils.getParameterByName('reporter');
+ expect(value).toBe('Console');
+ });
+
+ it('should return invalid parameter', () => {
+ const value = gl.utils.getParameterByName('fakeParameter');
+ expect(value).toBe(null);
+ });
+ });
});
})();
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js.es6 b/spec/javascripts/lib/utils/text_utility_spec.js.es6
new file mode 100644
index 00000000000..e97356b65d5
--- /dev/null
+++ b/spec/javascripts/lib/utils/text_utility_spec.js.es6
@@ -0,0 +1,25 @@
+//= require lib/utils/text_utility
+
+(() => {
+ describe('text_utility', () => {
+ describe('gl.text.getTextWidth', () => {
+ it('returns zero width when no text is passed', () => {
+ expect(gl.text.getTextWidth('')).toBe(0);
+ });
+
+ it('returns zero width when no text is passed and font is passed', () => {
+ expect(gl.text.getTextWidth('', '100px sans-serif')).toBe(0);
+ });
+
+ it('returns width when text is passed', () => {
+ expect(gl.text.getTextWidth('foo') > 0).toBe(true);
+ });
+
+ it('returns bigger width when font is larger', () => {
+ const largeFont = gl.text.getTextWidth('foo', '100px sans-serif');
+ const regular = gl.text.getTextWidth('foo', '10px sans-serif');
+ expect(largeFont > regular).toBe(true);
+ });
+ });
+ });
+})();
diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js
index decdf583410..31f516b41bf 100644
--- a/spec/javascripts/line_highlighter_spec.js
+++ b/spec/javascripts/line_highlighter_spec.js
@@ -6,7 +6,7 @@
(function() {
describe('LineHighlighter', function() {
var clickLine;
- fixture.preload('line_highlighter.html');
+ preloadFixtures('static/line_highlighter.html.raw');
clickLine = function(number, eventData) {
var e;
if (eventData == null) {
@@ -20,7 +20,7 @@
}
};
beforeEach(function() {
- fixture.load('line_highlighter.html');
+ loadFixtures('static/line_highlighter.html.raw');
this["class"] = new LineHighlighter();
this.css = this["class"].highlightClass;
return this.spies = {
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 4cf1693af1b..9b232617fe5 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -6,9 +6,9 @@
(function() {
describe('MergeRequest', function() {
return describe('task lists', function() {
- fixture.preload('merge_requests_show.html');
+ preloadFixtures('static/merge_requests_show.html.raw');
beforeEach(function() {
- fixture.load('merge_requests_show.html');
+ loadFixtures('static/merge_requests_show.html.raw');
return this.merge = new MergeRequest();
});
it('modifies the Markdown field', function() {
diff --git a/spec/javascripts/merge_request_tabs_spec.js b/spec/javascripts/merge_request_tabs_spec.js
index 130d391bfab..98201fb98ed 100644
--- a/spec/javascripts/merge_request_tabs_spec.js
+++ b/spec/javascripts/merge_request_tabs_spec.js
@@ -16,7 +16,7 @@
};
$.extend(stubLocation, defaults, stubs || {});
};
- fixture.preload('merge_request_tabs.html');
+ preloadFixtures('static/merge_request_tabs.html.raw');
beforeEach(function () {
this.class = new gl.MergeRequestTabs({ stubLocation: stubLocation });
@@ -30,7 +30,7 @@
describe('#activateTab', function () {
beforeEach(function () {
spyOn($, 'ajax').and.callFake(function () {});
- fixture.load('merge_request_tabs.html');
+ loadFixtures('static/merge_request_tabs.html.raw');
this.subject = this.class.activateTab;
});
it('shows the first tab when action is show', function () {
diff --git a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6 b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
index d1793e9308e..a1c2fe3df37 100644
--- a/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
+++ b/spec/javascripts/mini_pipeline_graph_dropdown_spec.js.es6
@@ -5,10 +5,10 @@
(() => {
describe('Mini Pipeline Graph Dropdown', () => {
- fixture.preload('mini_dropdown_graph');
+ preloadFixtures('static/mini_dropdown_graph.html.raw');
beforeEach(() => {
- fixture.load('mini_dropdown_graph');
+ loadFixtures('static/mini_dropdown_graph.html.raw');
});
describe('When is initialized', () => {
diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js
index a6cb9e47744..e0dc549a9f4 100644
--- a/spec/javascripts/new_branch_spec.js
+++ b/spec/javascripts/new_branch_spec.js
@@ -8,7 +8,7 @@
describe('Branch', function() {
return describe('create a new branch', function() {
var expectToHaveError, fillNameWith;
- fixture.preload('new_branch.html');
+ preloadFixtures('static/new_branch.html.raw');
fillNameWith = function(value) {
return $('.js-branch-name').val(value).trigger('blur');
};
@@ -16,7 +16,7 @@
return expect($('.js-branch-name-error span').text()).toEqual(error);
};
beforeEach(function() {
- fixture.load('new_branch.html');
+ loadFixtures('static/new_branch.html.raw');
$('form').on('submit', function(e) {
return e.preventDefault();
});
diff --git a/spec/javascripts/notes_spec.js b/spec/javascripts/notes_spec.js
index bb13af7ac0c..9cdb0a5d5aa 100644
--- a/spec/javascripts/notes_spec.js
+++ b/spec/javascripts/notes_spec.js
@@ -12,11 +12,11 @@
gl.utils = gl.utils || {};
describe('Notes', function() {
- var commentsTemplate = 'issues/issue_with_comment.raw';
- fixture.preload(commentsTemplate);
+ var commentsTemplate = 'issues/issue_with_comment.html.raw';
+ preloadFixtures(commentsTemplate);
beforeEach(function () {
- fixture.load(commentsTemplate);
+ loadFixtures(commentsTemplate);
gl.utils.disableButtonIfEmptyField = _.noop;
window.project_uploads_path = 'http://test.host/uploads';
$('body').data('page', 'projects:issues:show');
diff --git a/spec/javascripts/pipelines_spec.js.es6 b/spec/javascripts/pipelines_spec.js.es6
index 85c9cf4b4f1..f0f9ad7430d 100644
--- a/spec/javascripts/pipelines_spec.js.es6
+++ b/spec/javascripts/pipelines_spec.js.es6
@@ -2,10 +2,10 @@
(() => {
describe('Pipelines', () => {
- fixture.preload('pipeline_graph');
+ preloadFixtures('static/pipeline_graph.html.raw');
beforeEach(() => {
- fixture.load('pipeline_graph');
+ loadFixtures('static/pipeline_graph.html.raw');
});
it('should be defined', () => {
diff --git a/spec/javascripts/project_title_spec.js b/spec/javascripts/project_title_spec.js
index 216b77f37c0..27b071f266d 100644
--- a/spec/javascripts/project_title_spec.js
+++ b/spec/javascripts/project_title_spec.js
@@ -16,10 +16,9 @@
window.gon.api_version = 'v3';
describe('Project Title', function() {
- fixture.preload('project_title.html');
- fixture.preload('projects.json');
+ preloadFixtures('static/project_title.html.raw');
beforeEach(function() {
- fixture.load('project_title.html');
+ loadFixtures('static/project_title.html.raw');
return this.project = new Project();
});
return describe('project list', function() {
@@ -34,7 +33,7 @@
beforeEach((function(_this) {
return function() {
- _this.projects_data = fixture.load('projects.json')[0];
+ _this.projects_data = getJSONFixture('projects.json');
return spyOn(jQuery, 'ajax').and.callFake(fakeAjaxResponse.bind(_this));
};
})(this));
diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js
index a083dbf033a..0177d8e4e79 100644
--- a/spec/javascripts/right_sidebar_spec.js
+++ b/spec/javascripts/right_sidebar_spec.js
@@ -36,9 +36,9 @@
describe('RightSidebar', function() {
var fixtureName = 'issues/open-issue.html.raw';
- fixture.preload(fixtureName);
+ preloadFixtures(fixtureName);
beforeEach(function() {
- fixture.load(fixtureName);
+ loadFixtures(fixtureName);
this.sidebar = new Sidebar;
$aside = $('.right-sidebar');
$page = $('.page-with-sidebar');
@@ -65,9 +65,10 @@
});
it('should broadcast todo:toggle event when add todo clicked', function() {
+ var todos = getJSONFixture('todos.json');
spyOn(jQuery, 'ajax').and.callFake(function() {
var d = $.Deferred();
- var response = fixture.load('todos.json');
+ var response = todos;
d.resolve(response);
return d.promise();
});
diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js
index 1b7f642d59e..2d3f44e7980 100644
--- a/spec/javascripts/search_autocomplete_spec.js
+++ b/spec/javascripts/search_autocomplete_spec.js
@@ -11,6 +11,7 @@
(function() {
var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget;
+ var userName = 'root';
widget = null;
@@ -19,6 +20,7 @@
window.gon || (window.gon = {});
window.gon.current_user_id = userId;
+ window.gon.current_username = userName;
dashboardIssuesPath = '/dashboard/issues';
@@ -93,8 +95,8 @@
assertLinks = function(list, issuesPath, mrsPath) {
var a1, a2, a3, a4, issuesAssignedToMeLink, issuesIHaveCreatedLink, mrsAssignedToMeLink, mrsIHaveCreatedLink;
- issuesAssignedToMeLink = issuesPath + "/?assignee_id=" + userId;
- issuesIHaveCreatedLink = issuesPath + "/?author_id=" + userId;
+ issuesAssignedToMeLink = issuesPath + "/?assignee_username=" + userName;
+ issuesIHaveCreatedLink = issuesPath + "/?author_username=" + userName;
mrsAssignedToMeLink = mrsPath + "/?assignee_id=" + userId;
mrsIHaveCreatedLink = mrsPath + "/?author_id=" + userId;
a1 = "a[href='" + issuesAssignedToMeLink + "']";
@@ -112,9 +114,9 @@
};
describe('Search autocomplete dropdown', function() {
- fixture.preload('search_autocomplete.html');
+ preloadFixtures('static/search_autocomplete.html.raw');
beforeEach(function() {
- fixture.load('search_autocomplete.html');
+ loadFixtures('static/search_autocomplete.html.raw');
return widget = new gl.SearchAutocomplete;
});
it('should show Dashboard specific dropdown menu', function() {
diff --git a/spec/javascripts/shortcuts_issuable_spec.js b/spec/javascripts/shortcuts_issuable_spec.js
index 7bc898aed5d..ae5d639ad9c 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js
+++ b/spec/javascripts/shortcuts_issuable_spec.js
@@ -6,9 +6,9 @@
(function() {
describe('ShortcutsIssuable', function() {
var fixtureName = 'issues/open-issue.html.raw';
- fixture.preload(fixtureName);
+ preloadFixtures(fixtureName);
beforeEach(function() {
- fixture.load(fixtureName);
+ loadFixtures(fixtureName);
document.querySelector('.js-new-note-form').classList.add('js-main-target-form');
return this.shortcut = new ShortcutsIssuable();
});
diff --git a/spec/javascripts/signin_tabs_memoizer_spec.js.es6 b/spec/javascripts/signin_tabs_memoizer_spec.js.es6
index 9a9fb22255b..c274b9c45f4 100644
--- a/spec/javascripts/signin_tabs_memoizer_spec.js.es6
+++ b/spec/javascripts/signin_tabs_memoizer_spec.js.es6
@@ -2,7 +2,7 @@
((global) => {
describe('SigninTabsMemoizer', () => {
- const fixtureTemplate = 'signin_tabs.html';
+ const fixtureTemplate = 'static/signin_tabs.html.raw';
const tabSelector = 'ul.nav-tabs';
const currentTabKey = 'current_signin_tab';
let memo;
@@ -15,10 +15,10 @@
return memo;
}
- fixture.preload(fixtureTemplate);
+ preloadFixtures(fixtureTemplate);
beforeEach(() => {
- fixture.load(fixtureTemplate);
+ loadFixtures(fixtureTemplate);
});
it('does nothing if no tab was previously selected', () => {
diff --git a/spec/javascripts/smart_interval_spec.js.es6 b/spec/javascripts/smart_interval_spec.js.es6
index 1b7ca97cde4..39d236986b9 100644
--- a/spec/javascripts/smart_interval_spec.js.es6
+++ b/spec/javascripts/smart_interval_spec.js.es6
@@ -103,7 +103,7 @@
describe('DOM Events', function () {
beforeEach(function () {
// This ensures DOM and DOM events are initialized for these specs.
- fixture.set('<div></div>');
+ setFixtures('<div></div>');
this.smartInterval = createDefaultSmartInterval();
});
diff --git a/spec/javascripts/spec_helper.js b/spec/javascripts/spec_helper.js
index 831dfada952..f8e3aca29fa 100644
--- a/spec/javascripts/spec_helper.js
+++ b/spec/javascripts/spec_helper.js
@@ -37,12 +37,12 @@
// file as a manifest.
// For more information: http://github.com/modeset/teaspoon
-(function() {
-
-
-}).call(this);
+// set our fixtures path
+jasmine.getFixtures().fixturesPath = '/teaspoon/fixtures';
+jasmine.getJSONFixtures().fixturesPath = '/teaspoon/fixtures';
// defined in ActionDispatch::TestRequest
// see https://github.com/rails/rails/blob/v4.2.7.1/actionpack/lib/action_dispatch/testing/test_request.rb#L7
window.gl = window.gl || {};
-gl.TEST_HOST = 'http://test.host';
+window.gl.TEST_HOST = 'http://test.host';
+window.gon = window.gon || {};
diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js
index ac411f6c306..5984ce8ffd4 100644
--- a/spec/javascripts/syntax_highlight_spec.js
+++ b/spec/javascripts/syntax_highlight_spec.js
@@ -13,7 +13,7 @@
};
describe('on a js-syntax-highlight element', function() {
beforeEach(function() {
- return fixture.set('<div class="js-syntax-highlight"></div>');
+ return setFixtures('<div class="js-syntax-highlight"></div>');
});
return it('applies syntax highlighting', function() {
stubUserColorScheme('monokai');
@@ -23,7 +23,7 @@
});
return describe('on a parent element', function() {
beforeEach(function() {
- return fixture.set("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
+ return setFixtures("<div class=\"parent\">\n <div class=\"js-syntax-highlight\"></div>\n <div class=\"foo\"></div>\n <div class=\"js-syntax-highlight\"></div>\n</div>");
});
it('applies highlighting to all applicable children', function() {
stubUserColorScheme('monokai');
@@ -33,7 +33,7 @@
});
return it('prevents an infinite loop when no matches exist', function() {
var highlight;
- fixture.set('<div></div>');
+ setFixtures('<div></div>');
highlight = function() {
return $('div').syntaxHighlight();
};
diff --git a/spec/javascripts/u2f/authenticate_spec.js b/spec/javascripts/u2f/authenticate_spec.js
index 064d18519ea..dc2f4967985 100644
--- a/spec/javascripts/u2f/authenticate_spec.js
+++ b/spec/javascripts/u2f/authenticate_spec.js
@@ -10,8 +10,10 @@
(function() {
describe('U2FAuthenticate', function() {
- fixture.load('u2f/authenticate');
+ preloadFixtures('u2f/authenticate.html.raw');
+
beforeEach(function() {
+ loadFixtures('u2f/authenticate.html.raw');
this.u2fDevice = new MockU2FDevice;
this.container = $("#js-authenticate-u2f");
this.component = new window.gl.U2FAuthenticate(
diff --git a/spec/javascripts/u2f/register_spec.js b/spec/javascripts/u2f/register_spec.js
index 189592ea87a..ab4c5edd044 100644
--- a/spec/javascripts/u2f/register_spec.js
+++ b/spec/javascripts/u2f/register_spec.js
@@ -10,8 +10,10 @@
(function() {
describe('U2FRegister', function() {
- fixture.load('u2f/register');
+ preloadFixtures('u2f/register.html.raw');
+
beforeEach(function() {
+ loadFixtures('u2f/register.html.raw');
this.u2fDevice = new MockU2FDevice;
this.container = $("#js-register-u2f");
this.component = new U2FRegister(this.container, $("#js-register-u2f-templates"), {}, "token");
diff --git a/spec/javascripts/vue_common_components/commit_spec.js.es6 b/spec/javascripts/vue_common_components/commit_spec.js.es6
index 26dfdb94aae..d6c6f786fb1 100644
--- a/spec/javascripts/vue_common_components/commit_spec.js.es6
+++ b/spec/javascripts/vue_common_components/commit_spec.js.es6
@@ -5,7 +5,7 @@ describe('Commit component', () => {
let component;
it('should render a code-fork icon if it does not represent a tag', () => {
- fixture.set('<div class="test-commit-container"></div>');
+ setFixtures('<div class="test-commit-container"></div>');
component = new window.gl.CommitComponent({
el: document.querySelector('.test-commit-container'),
propsData: {
@@ -30,7 +30,7 @@ describe('Commit component', () => {
describe('Given all the props', () => {
beforeEach(() => {
- fixture.set('<div class="test-commit-container"></div>');
+ setFixtures('<div class="test-commit-container"></div>');
props = {
tag: true,
@@ -105,7 +105,7 @@ describe('Commit component', () => {
describe('When commit title is not provided', () => {
it('should render default message', () => {
- fixture.set('<div class="test-commit-container"></div>');
+ setFixtures('<div class="test-commit-container"></div>');
props = {
tag: false,
commitRef: {
diff --git a/spec/javascripts/vue_pagination/pagination_spec.js.es6 b/spec/javascripts/vue_pagination/pagination_spec.js.es6
new file mode 100644
index 00000000000..1a7f2bb5fb8
--- /dev/null
+++ b/spec/javascripts/vue_pagination/pagination_spec.js.es6
@@ -0,0 +1,168 @@
+//= require vue
+//= require lib/utils/common_utils
+//= require vue_pagination/index
+/* global fixture, gl */
+
+describe('Pagination component', () => {
+ let component;
+
+ const changeChanges = {
+ one: '',
+ two: '',
+ };
+
+ const change = (one, two) => {
+ changeChanges.one = one;
+ changeChanges.two = two;
+ };
+
+ it('should render and start at page 1', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 2,
+ previousPage: '',
+ },
+ change,
+ },
+ });
+
+ expect(component.$el.classList).toContain('gl-pagination');
+
+ component.changePage({ target: { innerText: '1' } });
+
+ expect(changeChanges.one).toEqual(1);
+ expect(changeChanges.two).toEqual('all');
+ });
+
+ it('should go to the previous page', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 3,
+ previousPage: 1,
+ },
+ change,
+ },
+ });
+
+ component.changePage({ target: { innerText: 'Prev' } });
+
+ expect(changeChanges.one).toEqual(1);
+ expect(changeChanges.two).toEqual('all');
+ });
+
+ it('should go to the next page', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 5,
+ previousPage: 3,
+ },
+ change,
+ },
+ });
+
+ component.changePage({ target: { innerText: 'Next' } });
+
+ expect(changeChanges.one).toEqual(5);
+ expect(changeChanges.two).toEqual('all');
+ });
+
+ it('should go to the last page', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 5,
+ previousPage: 3,
+ },
+ change,
+ },
+ });
+
+ component.changePage({ target: { innerText: 'Last >>' } });
+
+ expect(changeChanges.one).toEqual(10);
+ expect(changeChanges.two).toEqual('all');
+ });
+
+ it('should go to the first page', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 5,
+ previousPage: 3,
+ },
+ change,
+ },
+ });
+
+ component.changePage({ target: { innerText: '<< First' } });
+
+ expect(changeChanges.one).toEqual(1);
+ expect(changeChanges.two).toEqual('all');
+ });
+
+ it('should do nothing', () => {
+ fixture.set('<div class="test-pagination-container"></div>');
+
+ component = new window.gl.VueGlPagination({
+ el: document.querySelector('.test-pagination-container'),
+ propsData: {
+ pageInfo: {
+ totalPages: 10,
+ nextPage: 2,
+ previousPage: '',
+ },
+ change,
+ },
+ });
+
+ component.changePage({ target: { innerText: '...' } });
+
+ expect(changeChanges.one).toEqual(1);
+ expect(changeChanges.two).toEqual('all');
+ });
+});
+
+describe('paramHelper', () => {
+ it('can parse url parameters correctly', () => {
+ window.history.pushState({}, null, '?scope=all&p=2');
+
+ const scope = gl.utils.getParameterByName('scope');
+ const p = gl.utils.getParameterByName('p');
+
+ expect(scope).toEqual('all');
+ expect(p).toEqual('2');
+ });
+
+ it('returns null if param not in url', () => {
+ window.history.pushState({}, null, '?p=2');
+
+ const scope = gl.utils.getParameterByName('scope');
+ const p = gl.utils.getParameterByName('p');
+
+ expect(scope).toEqual(null);
+ expect(p).toEqual('2');
+ });
+});
diff --git a/spec/javascripts/zen_mode_spec.js b/spec/javascripts/zen_mode_spec.js
index 5b4d007c8f7..f1c2edcc55c 100644
--- a/spec/javascripts/zen_mode_spec.js
+++ b/spec/javascripts/zen_mode_spec.js
@@ -10,9 +10,9 @@
describe('ZenMode', function() {
var fixtureName = 'issues/open-issue.html.raw';
- fixture.preload(fixtureName);
+ preloadFixtures(fixtureName);
beforeEach(function() {
- fixture.load(fixtureName);
+ loadFixtures(fixtureName);
spyOn(Dropzone, 'forElement').and.callFake(function() {
return {
enable: function() {
diff --git a/spec/lib/api/helpers/pagination_spec.rb b/spec/lib/api/helpers/pagination_spec.rb
new file mode 100644
index 00000000000..267318faed4
--- /dev/null
+++ b/spec/lib/api/helpers/pagination_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe API::Helpers::Pagination do
+ let(:resource) { Project.all }
+
+ subject do
+ Class.new.include(described_class).new
+ end
+
+ describe '#paginate' do
+ let(:value) { spy('return value') }
+
+ before do
+ allow(value).to receive(:to_query).and_return(value)
+
+ allow(subject).to receive(:header).and_return(value)
+ allow(subject).to receive(:params).and_return(value)
+ allow(subject).to receive(:request).and_return(value)
+ end
+
+ describe 'required instance methods' do
+ let(:return_spy) { spy }
+
+ it 'requires some instance methods' do
+ expect_message(:header)
+ expect_message(:params)
+ expect_message(:request)
+
+ subject.paginate(resource)
+ end
+ end
+
+ context 'when resource can be paginated' do
+ before do
+ create_list(:empty_project, 3)
+ end
+
+ describe 'first page' do
+ before do
+ allow(subject).to receive(:params)
+ .and_return({ page: 1, per_page: 2 })
+ end
+
+ it 'returns appropriate amount of resources' do
+ expect(subject.paginate(resource).count).to eq 2
+ end
+
+ it 'adds appropriate headers' do
+ expect_header('X-Total', '3')
+ expect_header('X-Total-Pages', '2')
+ expect_header('X-Per-Page', '2')
+ expect_header('X-Page', '1')
+ expect_header('X-Next-Page', '2')
+ expect_header('X-Prev-Page', '')
+ expect_header('Link', any_args)
+
+ subject.paginate(resource)
+ end
+ end
+
+ describe 'second page' do
+ before do
+ allow(subject).to receive(:params)
+ .and_return({ page: 2, per_page: 2 })
+ end
+
+ it 'returns appropriate amount of resources' do
+ expect(subject.paginate(resource).count).to eq 1
+ end
+
+ it 'adds appropriate headers' do
+ expect_header('X-Total', '3')
+ expect_header('X-Total-Pages', '2')
+ expect_header('X-Per-Page', '2')
+ expect_header('X-Page', '2')
+ expect_header('X-Next-Page', '')
+ expect_header('X-Prev-Page', '1')
+ expect_header('Link', any_args)
+
+ subject.paginate(resource)
+ end
+ end
+ end
+
+ def expect_header(name, value)
+ expect(subject).to receive(:header).with(name, value)
+ end
+
+ def expect_message(method)
+ expect(subject).to receive(method)
+ .at_least(:once).and_return(value)
+ end
+ end
+end
diff --git a/spec/lib/ci/ansi2html_spec.rb b/spec/lib/ci/ansi2html_spec.rb
index 898f1e84ab0..0762fd7e56a 100644
--- a/spec/lib/ci/ansi2html_spec.rb
+++ b/spec/lib/ci/ansi2html_spec.rb
@@ -136,6 +136,14 @@ describe Ci::Ansi2html, lib: true do
expect(subject.convert("<")[:html]).to eq('&lt;')
end
+ it "replaces newlines with line break tags" do
+ expect(subject.convert("\n")[:html]).to eq('<br>')
+ end
+
+ it "groups carriage returns with newlines" do
+ expect(subject.convert("\r\n")[:html]).to eq('<br>')
+ end
+
describe "incremental update" do
shared_examples 'stateable converter' do
let(:pass1) { subject.convert(pre_text) }
diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
index 62d68721574..f824e2e1efe 100644
--- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
+++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb
@@ -769,6 +769,19 @@ module Ci
expect(builds.first[:environment]).to eq(environment[:name])
expect(builds.first[:options]).to include(environment: environment)
end
+
+ context 'the url has a port as variable' do
+ let(:environment) do
+ { name: 'production',
+ url: 'http://production.gitlab.com:$PORT' }
+ end
+
+ it 'allows a variable for the port' do
+ expect(builds.size).to eq(1)
+ expect(builds.first[:environment]).to eq(environment[:name])
+ expect(builds.first[:options]).to include(environment: environment)
+ end
+ end
end
context 'when no environment is specified' do
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index f3843ca64ff..ba199917f5c 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -8,6 +8,10 @@ module Gitlab
let(:html) { 'H<sub>2</sub>O' }
context "without project" do
+ before do
+ allow_any_instance_of(ApplicationSetting).to receive(:current).and_return(::ApplicationSetting.create_from_defaults)
+ end
+
it "converts the input using Asciidoctor and default options" do
expected_asciidoc_opts = {
safe: :secure,
diff --git a/spec/lib/gitlab/backup/manager_spec.rb b/spec/lib/gitlab/backup/manager_spec.rb
index 1b749d1bd39..f84782ab440 100644
--- a/spec/lib/gitlab/backup/manager_spec.rb
+++ b/spec/lib/gitlab/backup/manager_spec.rb
@@ -1,9 +1,27 @@
require 'spec_helper'
describe Backup::Manager, lib: true do
- describe '#remove_old' do
- let(:progress) { StringIO.new }
+ include StubENV
+
+ let(:progress) { StringIO.new }
+
+ before do
+ allow(progress).to receive(:puts)
+ allow(progress).to receive(:print)
+
+ allow_any_instance_of(String).to receive(:color) do |string, _color|
+ string
+ end
+
+ @old_progress = $progress # rubocop:disable Style/GlobalVars
+ $progress = progress # rubocop:disable Style/GlobalVars
+ end
+
+ after do
+ $progress = @old_progress # rubocop:disable Style/GlobalVars
+ end
+ describe '#remove_old' do
let(:files) do
[
'1451606400_2016_01_01_gitlab_backup.tar',
@@ -20,20 +38,6 @@ describe Backup::Manager, lib: true do
allow(Dir).to receive(:glob).and_return(files)
allow(FileUtils).to receive(:rm)
allow(Time).to receive(:now).and_return(Time.utc(2016))
-
- allow(progress).to receive(:puts)
- allow(progress).to receive(:print)
-
- allow_any_instance_of(String).to receive(:color) do |string, _color|
- string
- end
-
- @old_progress = $progress # rubocop:disable Style/GlobalVars
- $progress = progress # rubocop:disable Style/GlobalVars
- end
-
- after do
- $progress = @old_progress # rubocop:disable Style/GlobalVars
end
context 'when keep_time is zero' do
@@ -124,4 +128,82 @@ describe Backup::Manager, lib: true do
end
end
end
+
+ describe '#unpack' do
+ before do
+ allow(Dir).to receive(:chdir)
+ end
+
+ context 'when there are no backup files in the directory' do
+ before do
+ allow(Dir).to receive(:glob).and_return([])
+ end
+
+ it 'fails the operation and prints an error' do
+ expect { subject.unpack }.to raise_error SystemExit
+ expect(progress).to have_received(:puts)
+ .with(a_string_matching('No backups found'))
+ end
+ end
+
+ context 'when there are two backup files in the directory and BACKUP variable is not set' do
+ before do
+ allow(Dir).to receive(:glob).and_return(
+ [
+ '1451606400_2016_01_01_gitlab_backup.tar',
+ '1451520000_2015_12_31_gitlab_backup.tar',
+ ]
+ )
+ end
+
+ it 'fails the operation and prints an error' do
+ expect { subject.unpack }.to raise_error SystemExit
+ expect(progress).to have_received(:puts)
+ .with(a_string_matching('Found more than one backup'))
+ end
+ end
+
+ context 'when BACKUP variable is set to a non-existing file' do
+ before do
+ allow(Dir).to receive(:glob).and_return(
+ [
+ '1451606400_2016_01_01_gitlab_backup.tar'
+ ]
+ )
+ allow(File).to receive(:exist?).and_return(false)
+
+ stub_env('BACKUP', 'wrong')
+ end
+
+ it 'fails the operation and prints an error' do
+ expect { subject.unpack }.to raise_error SystemExit
+ expect(File).to have_received(:exist?).with('wrong_gitlab_backup.tar')
+ expect(progress).to have_received(:puts)
+ .with(a_string_matching('The backup file wrong_gitlab_backup.tar does not exist'))
+ end
+ end
+
+ context 'when BACKUP variable is set to a correct file' do
+ before do
+ allow(Dir).to receive(:glob).and_return(
+ [
+ '1451606400_2016_01_01_gitlab_backup.tar'
+ ]
+ )
+ allow(File).to receive(:exist?).and_return(true)
+ allow(Kernel).to receive(:system).and_return(true)
+ allow(YAML).to receive(:load_file).and_return(gitlab_version: Gitlab::VERSION)
+
+ stub_env('BACKUP', '1451606400_2016_01_01')
+ end
+
+ it 'unpacks the file' do
+ subject.unpack
+
+ expect(Kernel).to have_received(:system)
+ .with("tar", "-xf", "1451606400_2016_01_01_gitlab_backup.tar")
+ expect(progress).to have_received(:puts).with(a_string_matching('done'))
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/checks/change_access_spec.rb b/spec/lib/gitlab/checks/change_access_spec.rb
index 39069b49978..98effecdbbc 100644
--- a/spec/lib/gitlab/checks/change_access_spec.rb
+++ b/spec/lib/gitlab/checks/change_access_spec.rb
@@ -56,7 +56,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
- expect(user_access).to receive(:can_do_action?).with(:force_push_code_to_protected_branches).and_return(false)
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
@@ -88,8 +87,6 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end
it 'returns an error if the user is not allowed to delete protected branches' do
- expect(user_access).to receive(:can_do_action?).with(:remove_protected_branches).and_return(false)
-
expect(subject.status).to be(false)
expect(subject.message).to eq('You are not allowed to delete protected branches from this project.')
end
diff --git a/spec/lib/gitlab/ci/config/entry/environment_spec.rb b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
index d97806295fb..2adbed2154f 100644
--- a/spec/lib/gitlab/ci/config/entry/environment_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/environment_spec.rb
@@ -196,22 +196,5 @@ describe Gitlab::Ci::Config::Entry::Environment do
end
end
end
-
- context 'when invalid URL is used' do
- let(:config) { { name: 'test', url: 'invalid-example.gitlab.com' } }
-
- describe '#valid?' do
- it 'is not valid' do
- expect(entry).not_to be_valid
- end
- end
-
- describe '#errors?' do
- it 'contains error about invalid URL' do
- expect(entry.errors)
- .to include "environment url must be a valid url"
- end
- end
- end
end
end
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index ac26c831fd0..d88a141b458 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -248,6 +248,7 @@ DeployKey:
- fingerprint
- public
- can_push
+- last_used_at
Service:
- id
- type
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 534bcbf39fe..b9d12c3c24c 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -15,9 +15,9 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
it 'should block user in GitLab' do
+ expect(access).to receive(:block_user).with(user, 'does not exist anymore')
+
access.allowed?
- expect(user).to be_blocked
- expect(user).to be_ldap_blocked
end
end
@@ -34,9 +34,9 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
it 'blocks user in GitLab' do
+ expect(access).to receive(:block_user).with(user, 'is disabled in Active Directory')
+
access.allowed?
- expect(user).to be_blocked
- expect(user).to be_ldap_blocked
end
end
@@ -53,7 +53,10 @@ describe Gitlab::LDAP::Access, lib: true do
end
it 'does not unblock user in GitLab' do
+ expect(access).not_to receive(:unblock_user)
+
access.allowed?
+
expect(user).to be_blocked
expect(user).not_to be_ldap_blocked # this block is handled by omniauth not by our internal logic
end
@@ -65,8 +68,9 @@ describe Gitlab::LDAP::Access, lib: true do
end
it 'unblocks user in GitLab' do
+ expect(access).to receive(:unblock_user).with(user, 'is not disabled anymore')
+
access.allowed?
- expect(user).not_to be_blocked
end
end
end
@@ -87,9 +91,9 @@ describe Gitlab::LDAP::Access, lib: true do
it { is_expected.to be_falsey }
it 'blocks user in GitLab' do
+ expect(access).to receive(:block_user).with(user, 'does not exist anymore')
+
access.allowed?
- expect(user).to be_blocked
- expect(user).to be_ldap_blocked
end
end
@@ -99,11 +103,54 @@ describe Gitlab::LDAP::Access, lib: true do
end
it 'unblocks the user if it exists' do
+ expect(access).to receive(:unblock_user).with(user, 'is available again')
+
access.allowed?
- expect(user).not_to be_blocked
end
end
end
end
end
+
+ describe '#block_user' do
+ before do
+ user.activate
+ allow(Gitlab::AppLogger).to receive(:info)
+
+ access.block_user user, 'reason'
+ end
+
+ it 'blocks the user' do
+ expect(user).to be_blocked
+ expect(user).to be_ldap_blocked
+ end
+
+ it 'logs the reason' do
+ expect(Gitlab::AppLogger).to have_received(:info).with(
+ "LDAP account \"123456\" reason, " \
+ "blocking Gitlab user \"#{user.name}\" (#{user.email})"
+ )
+ end
+ end
+
+ describe '#unblock_user' do
+ before do
+ user.ldap_block
+ allow(Gitlab::AppLogger).to receive(:info)
+
+ access.unblock_user user, 'reason'
+ end
+
+ it 'activates the user' do
+ expect(user).not_to be_blocked
+ expect(user).not_to be_ldap_blocked
+ end
+
+ it 'logs the reason' do
+ Gitlab::AppLogger.info(
+ "LDAP account \"123456\" reason, " \
+ "unblocking Gitlab user \"#{user.name}\" (#{user.email})"
+ )
+ end
+ end
end
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 1a6803e01c3..cab2e9908ff 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -129,4 +129,27 @@ describe Gitlab::LDAP::Config, lib: true do
expect(config.has_auth?).to be_falsey
end
end
+
+ describe '#attributes' do
+ it 'uses default attributes when no custom attributes are configured' do
+ expect(config.attributes).to eq(config.default_attributes)
+ end
+
+ it 'merges the configuration attributes with default attributes' do
+ stub_ldap_config(
+ options: {
+ 'attributes' => {
+ 'username' => %w(sAMAccountName),
+ 'email' => %w(userPrincipalName)
+ }
+ }
+ )
+
+ expect(config.attributes).to include({
+ 'username' => %w(sAMAccountName),
+ 'email' => %w(userPrincipalName),
+ 'name' => 'cn'
+ })
+ end
+ end
end
diff --git a/spec/lib/gitlab/ldap/person_spec.rb b/spec/lib/gitlab/ldap/person_spec.rb
index 60afe046788..9a556cde5d5 100644
--- a/spec/lib/gitlab/ldap/person_spec.rb
+++ b/spec/lib/gitlab/ldap/person_spec.rb
@@ -7,9 +7,11 @@ describe Gitlab::LDAP::Person do
before do
stub_ldap_config(
- attributes: {
- name: 'cn',
- email: %w(mail email userPrincipalName)
+ options: {
+ 'attributes' => {
+ 'name' => 'cn',
+ 'email' => %w(mail email userPrincipalName)
+ }
}
)
end
@@ -30,7 +32,7 @@ describe Gitlab::LDAP::Person do
entry['mail'] = mail
person = Gitlab::LDAP::Person.new(entry, 'ldapmain')
- expect(person.email).to eq(mail)
+ expect(person.email).to eq([mail])
end
it 'returns the value of userPrincipalName, if mail and email are not present' do
@@ -38,7 +40,7 @@ describe Gitlab::LDAP::Person do
entry['userPrincipalName'] = user_principal_name
person = Gitlab::LDAP::Person.new(entry, 'ldapmain')
- expect(person.email).to eq(user_principal_name)
+ expect(person.email).to eq([user_principal_name])
end
end
end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index 7371b578a48..fb470ea7568 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -126,5 +126,16 @@ describe Gitlab::Metrics::RackMiddleware do
expect(transaction.action).to eq('Grape#GET /projects/:id/archive')
end
+
+ it 'does not tag a transaction if route infos are missing' do
+ endpoint = double(:endpoint)
+ allow(endpoint).to receive(:route).and_raise
+
+ env['api.endpoint'] = endpoint
+
+ middleware.tag_endpoint(transaction, env)
+
+ expect(transaction.action).to be_nil
+ end
end
end
diff --git a/spec/lib/gitlab/redis_spec.rb b/spec/lib/gitlab/redis_spec.rb
index e5406fb2d33..917c5c46db1 100644
--- a/spec/lib/gitlab/redis_spec.rb
+++ b/spec/lib/gitlab/redis_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::Redis do
- let(:redis_config) { Rails.root.join('config', 'resque.yml').to_s }
+ include StubENV
before(:each) { clear_raw_config }
after(:each) { clear_raw_config }
@@ -72,6 +72,20 @@ describe Gitlab::Redis do
expect(url2).not_to end_with('foobar')
end
+
+ context 'when yml file with env variable' do
+ let(:redis_config) { Rails.root.join('spec/fixtures/config/redis_config_with_env.yml') }
+
+ before do
+ stub_env('TEST_GITLAB_REDIS_URL', 'redis://redishost:6379')
+ end
+
+ it 'reads redis url from env variable' do
+ stub_const("#{described_class}::CONFIG_FILE", redis_config)
+
+ expect(described_class.url).to eq 'redis://redishost:6379'
+ end
+ end
end
describe '._raw_config' do
diff --git a/spec/migrations/fill_authorized_projects_spec.rb b/spec/migrations/fill_authorized_projects_spec.rb
new file mode 100644
index 00000000000..99dc4195818
--- /dev/null
+++ b/spec/migrations/fill_authorized_projects_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20170106142508_fill_authorized_projects.rb')
+
+describe FillAuthorizedProjects do
+ describe '#up' do
+ it 'schedules the jobs in batches' do
+ user1 = create(:user)
+ user2 = create(:user)
+
+ expect(Sidekiq::Client).to receive(:push_bulk).with(
+ 'class' => 'AuthorizedProjectsWorker',
+ 'args' => [[user1.id], [user2.id]]
+ )
+
+ described_class.new.up
+ end
+ end
+end
diff --git a/spec/migrations/remove_dot_git_from_usernames.rb b/spec/migrations/remove_dot_git_from_usernames.rb
deleted file mode 100644
index 1b1d2adc463..00000000000
--- a/spec/migrations/remove_dot_git_from_usernames.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-# encoding: utf-8
-
-require 'spec_helper'
-require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
-
-describe RemoveDotGitFromUsernames do
- let(:user) { create(:user) }
-
- describe '#up' do
- let(:migration) { described_class.new }
-
- before do
- namespace = user.namespace
- namespace.path = 'test.git'
- namespace.save!(validate: false)
-
- user.username = 'test.git'
- user.save!(validate: false)
- end
-
- it 'renames user with .git in username' do
- migration.up
-
- expect(user.reload.username).to eq('test_git')
- expect(user.namespace.reload.path).to eq('test_git')
- expect(user.namespace.route.path).to eq('test_git')
- end
- end
-end
diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb
new file mode 100644
index 00000000000..8737e00eaeb
--- /dev/null
+++ b/spec/migrations/remove_dot_git_from_usernames_spec.rb
@@ -0,0 +1,57 @@
+# encoding: utf-8
+
+require 'spec_helper'
+require Rails.root.join('db', 'migrate', '20161226122833_remove_dot_git_from_usernames.rb')
+
+describe RemoveDotGitFromUsernames do
+ let(:user) { create(:user) }
+ let(:migration) { described_class.new }
+
+ describe '#up' do
+ before do
+ update_namespace(user, 'test.git')
+ end
+
+ it 'renames user with .git in username' do
+ migration.up
+
+ expect(user.reload.username).to eq('test_git')
+ expect(user.namespace.reload.path).to eq('test_git')
+ expect(user.namespace.route.path).to eq('test_git')
+ end
+ end
+
+ context 'when new path exists already' do
+ describe '#up' do
+ let(:user2) { create(:user) }
+
+ before do
+ update_namespace(user, 'test.git')
+ update_namespace(user2, 'test_git')
+
+ storages = { 'default' => 'tmp/tests/custom_repositories' }
+
+ allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
+ allow(migration).to receive(:route_exists?).with('test_git').and_return(true)
+ allow(migration).to receive(:route_exists?).with('test_git1').and_return(false)
+ end
+
+ it 'renames user with .git in username' do
+ migration.up
+
+ expect(user.reload.username).to eq('test_git1')
+ expect(user.namespace.reload.path).to eq('test_git1')
+ expect(user.namespace.route.path).to eq('test_git1')
+ end
+ end
+ end
+
+ def update_namespace(user, path)
+ namespace = user.namespace
+ namespace.path = path
+ namespace.save!(validate: false)
+
+ user.username = path
+ user.save!(validate: false)
+ end
+end
diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb
deleted file mode 100644
index cd3b6d51545..00000000000
--- a/spec/models/build_spec.rb
+++ /dev/null
@@ -1,1338 +0,0 @@
-require 'spec_helper'
-
-describe Ci::Build, models: true do
- let(:project) { create(:project) }
-
- let(:pipeline) do
- create(:ci_pipeline, project: project,
- sha: project.commit.id,
- ref: project.default_branch,
- status: 'success')
- end
-
- let(:build) { create(:ci_build, pipeline: pipeline) }
-
- it { is_expected.to validate_presence_of :ref }
-
- it { is_expected.to respond_to :trace_html }
-
- describe '#first_pending' do
- let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
- let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
- subject { Ci::Build.first_pending }
-
- it { is_expected.to be_a(Ci::Build) }
- it('returns with the first pending build') { is_expected.to eq(first) }
- end
-
- describe '#create_from' do
- before do
- build.status = 'success'
- build.save
- end
- let(:create_from_build) { Ci::Build.create_from build }
-
- it 'exists a pending task' do
- expect(Ci::Build.pending.count(:all)).to eq 0
- create_from_build
- expect(Ci::Build.pending.count(:all)).to be > 0
- end
- end
-
- describe '#failed_but_allowed?' do
- subject { build.failed_but_allowed? }
-
- context 'when build is not allowed to fail' do
- before do
- build.allow_failure = false
- end
-
- context 'and build.status is success' do
- before do
- build.status = 'success'
- end
-
- it { is_expected.to be_falsey }
- end
-
- context 'and build.status is failed' do
- before do
- build.status = 'failed'
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when build is allowed to fail' do
- before do
- build.allow_failure = true
- end
-
- context 'and build.status is success' do
- before do
- build.status = 'success'
- end
-
- it { is_expected.to be_falsey }
- end
-
- context 'and build.status is failed' do
- before do
- build.status = 'failed'
- end
-
- it { is_expected.to be_truthy }
- end
- end
- end
-
- describe '#persisted_environment' do
- before do
- @environment = create(:environment, project: project, name: "foo-#{project.default_branch}")
- end
-
- subject { build.persisted_environment }
-
- context 'referenced literally' do
- let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") }
-
- it { is_expected.to eq(@environment) }
- end
-
- context 'referenced with a variable' do
- let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
-
- it { is_expected.to eq(@environment) }
- end
- end
-
- describe '#trace' do
- it { expect(build.trace).to be_nil }
-
- context 'when build.trace contains text' do
- let(:text) { 'example output' }
- before do
- build.trace = text
- end
-
- it { expect(build.trace).to eq(text) }
- end
-
- context 'when build.trace hides runners token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.update(trace: token)
- build.project.update(runners_token: token)
- end
-
- it { expect(build.trace).not_to include(token) }
- it { expect(build.raw_trace).to include(token) }
- end
-
- context 'when build.trace hides build token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.update(trace: token)
- build.update(token: token)
- end
-
- it { expect(build.trace).not_to include(token) }
- it { expect(build.raw_trace).to include(token) }
- end
- end
-
- describe '#raw_trace' do
- subject { build.raw_trace }
-
- context 'when build.trace hides runners token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.project.update(runners_token: token)
- build.update(trace: token)
- end
-
- it { is_expected.not_to include(token) }
- end
-
- context 'when build.trace hides build token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.update(token: token)
- build.update(trace: token)
- end
-
- it { is_expected.not_to include(token) }
- end
- end
-
- context '#append_trace' do
- subject { build.trace_html }
-
- context 'when build.trace hides runners token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.project.update(runners_token: token)
- build.append_trace(token, 0)
- end
-
- it { is_expected.not_to include(token) }
- end
-
- context 'when build.trace hides build token' do
- let(:token) { 'my_secret_token' }
-
- before do
- build.update(token: token)
- build.append_trace(token, 0)
- end
-
- it { is_expected.not_to include(token) }
- end
- end
-
- # TODO: build timeout
- # describe :timeout do
- # subject { build.timeout }
- #
- # it { is_expected.to eq(pipeline.project.timeout) }
- # end
-
- describe '#options' do
- let(:options) do
- {
- image: "ruby:2.1",
- services: [
- "postgres"
- ]
- }
- end
-
- subject { build.options }
- it { is_expected.to eq(options) }
- end
-
- # TODO: allow_git_fetch
- # describe :allow_git_fetch do
- # subject { build.allow_git_fetch }
- #
- # it { is_expected.to eq(project.allow_git_fetch) }
- # end
-
- describe '#project' do
- subject { build.project }
-
- it { is_expected.to eq(pipeline.project) }
- end
-
- describe '#project_id' do
- subject { build.project_id }
-
- it { is_expected.to eq(pipeline.project_id) }
- end
-
- describe '#project_name' do
- subject { build.project_name }
-
- it { is_expected.to eq(project.name) }
- end
-
- describe '#extract_coverage' do
- context 'valid content & regex' do
- subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
-
- it { is_expected.to eq(98.29) }
- end
-
- context 'valid content & bad regex' do
- subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') }
-
- it { is_expected.to be_nil }
- end
-
- context 'no coverage content & regex' do
- subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') }
-
- it { is_expected.to be_nil }
- end
-
- context 'multiple results in content & regex' do
- subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') }
-
- it { is_expected.to eq(98.29) }
- end
-
- context 'using a regex capture' do
- subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
-
- it { is_expected.to eq(65) }
- end
- end
-
- describe '#ref_slug' do
- {
- 'master' => 'master',
- '1-foo' => '1-foo',
- 'fix/1-foo' => 'fix-1-foo',
- 'fix-1-foo' => 'fix-1-foo',
- 'a' * 63 => 'a' * 63,
- 'a' * 64 => 'a' * 63,
- 'FOO' => 'foo',
- }.each do |ref, slug|
- it "transforms #{ref} to #{slug}" do
- build.ref = ref
-
- expect(build.ref_slug).to eq(slug)
- end
- end
- end
-
- describe '#variables' do
- let(:container_registry_enabled) { false }
- let(:predefined_variables) do
- [
- { key: 'CI', value: 'true', public: true },
- { key: 'GITLAB_CI', value: 'true', public: true },
- { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
- { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
- { key: 'CI_BUILD_REF', value: build.sha, public: true },
- { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
- { key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
- { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
- { key: 'CI_BUILD_NAME', value: 'test', public: true },
- { key: 'CI_BUILD_STAGE', value: 'test', public: true },
- { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
- { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
- { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
- { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
- { key: 'CI_PROJECT_NAME', value: project.path, public: true },
- { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
- { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
- { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
- { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
- ]
- end
-
- before do
- stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
- end
-
- subject { build.variables }
-
- context 'returns variables' do
- before do
- build.yaml_variables = []
- end
-
- it { is_expected.to eq(predefined_variables) }
- end
-
- context 'when build has user' do
- let(:user) { create(:user, username: 'starter') }
- let(:user_variables) do
- [
- { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
- { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
- ]
- end
-
- before do
- build.update_attributes(user: user)
- end
-
- it { user_variables.each { |v| is_expected.to include(v) } }
- end
-
- context 'when build has an environment' do
- before do
- build.update(environment: 'production')
- create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
- end
-
- let(:environment_variables) do
- [
- { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
- { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
- ]
- end
-
- it { environment_variables.each { |v| is_expected.to include(v) } }
- end
-
- context 'when build started manually' do
- before do
- build.update_attributes(when: :manual)
- end
-
- let(:manual_variable) do
- { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
- end
-
- it { is_expected.to include(manual_variable) }
- end
-
- context 'when build is for tag' do
- let(:tag_variable) do
- { key: 'CI_BUILD_TAG', value: 'master', public: true }
- end
-
- before do
- build.update_attributes(tag: true)
- end
-
- it { is_expected.to include(tag_variable) }
- end
-
- context 'when secure variable is defined' do
- let(:secure_variable) do
- { key: 'SECRET_KEY', value: 'secret_value', public: false }
- end
-
- before do
- build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
- end
-
- it { is_expected.to include(secure_variable) }
- end
-
- context 'when build is for triggers' do
- let(:trigger) { create(:ci_trigger, project: project) }
- let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
- let(:user_trigger_variable) do
- { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
- end
- let(:predefined_trigger_variable) do
- { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
- end
-
- before do
- build.trigger_request = trigger_request
- end
-
- it { is_expected.to include(user_trigger_variable) }
- it { is_expected.to include(predefined_trigger_variable) }
- end
-
- context 'when yaml_variables are undefined' do
- before do
- build.yaml_variables = nil
- end
-
- context 'use from gitlab-ci.yml' do
- before do
- stub_ci_pipeline_yaml_file(config)
- end
-
- context 'when config is not found' do
- let(:config) { nil }
-
- it { is_expected.to eq(predefined_variables) }
- end
-
- context 'when config does not have a questioned job' do
- let(:config) do
- YAML.dump({
- test_other: {
- script: 'Hello World'
- }
- })
- end
-
- it { is_expected.to eq(predefined_variables) }
- end
-
- context 'when config has variables' do
- let(:config) do
- YAML.dump({
- test: {
- script: 'Hello World',
- variables: {
- KEY: 'value'
- }
- }
- })
- end
- let(:variables) do
- [{ key: 'KEY', value: 'value', public: true }]
- end
-
- it { is_expected.to eq(predefined_variables + variables) }
- end
- end
- end
-
- context 'when container registry is enabled' do
- let(:container_registry_enabled) { true }
- let(:ci_registry) do
- { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
- end
- let(:ci_registry_image) do
- { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true }
- end
-
- context 'and is disabled for project' do
- before do
- project.update(container_registry_enabled: false)
- end
-
- it { is_expected.to include(ci_registry) }
- it { is_expected.not_to include(ci_registry_image) }
- end
-
- context 'and is enabled for project' do
- before do
- project.update(container_registry_enabled: true)
- end
-
- it { is_expected.to include(ci_registry) }
- it { is_expected.to include(ci_registry_image) }
- end
- end
-
- context 'when runner is assigned to build' do
- let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
-
- before do
- build.update(runner: runner)
- end
-
- it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
- it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
- end
-
- context 'when build is for a deployment' do
- let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
-
- before do
- build.environment = 'production'
- allow(project).to receive(:deployment_variables).and_return([deployment_variable])
- end
-
- it { is_expected.to include(deployment_variable) }
- end
-
- context 'returns variables in valid order' do
- before do
- allow(build).to receive(:predefined_variables) { ['predefined'] }
- allow(project).to receive(:predefined_variables) { ['project'] }
- allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
- allow(build).to receive(:yaml_variables) { ['yaml'] }
- allow(project).to receive(:secret_variables) { ['secret'] }
- end
-
- it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
- end
- end
-
- describe '#has_tags?' do
- context 'when build has tags' do
- subject { create(:ci_build, tag_list: ['tag']) }
- it { is_expected.to have_tags }
- end
-
- context 'when build does not have tags' do
- subject { create(:ci_build, tag_list: []) }
- it { is_expected.not_to have_tags }
- end
- end
-
- describe '#any_runners_online?' do
- subject { build.any_runners_online? }
-
- context 'when no runners' do
- it { is_expected.to be_falsey }
- end
-
- context 'when there are runners' do
- let(:runner) { create(:ci_runner) }
-
- before do
- build.project.runners << runner
- runner.update_attributes(contacted_at: 1.second.ago)
- end
-
- it { is_expected.to be_truthy }
-
- it 'that is inactive' do
- runner.update_attributes(active: false)
- is_expected.to be_falsey
- end
-
- it 'that is not online' do
- runner.update_attributes(contacted_at: nil)
- is_expected.to be_falsey
- end
-
- it 'that cannot handle build' do
- expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
- is_expected.to be_falsey
- end
- end
- end
-
- describe '#stuck?' do
- subject { build.stuck? }
-
- context "when commit_status.status is pending" do
- before do
- build.status = 'pending'
- end
-
- it { is_expected.to be_truthy }
-
- context "and there are specific runner" do
- let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
-
- before do
- build.project.runners << runner
- runner.save
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- %w[success failed canceled running].each do |state|
- context "when commit_status.status is #{state}" do
- before do
- build.status = state
- end
-
- it { is_expected.to be_falsey }
- end
- end
- end
-
- describe '#artifacts?' do
- subject { build.artifacts? }
-
- context 'artifacts archive does not exist' do
- before do
- build.update_attributes(artifacts_file: nil)
- end
-
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive exists' do
- let(:build) { create(:ci_build, :artifacts) }
- it { is_expected.to be_truthy }
-
- context 'is expired' do
- before { build.update(artifacts_expire_at: Time.now - 7.days) }
- it { is_expected.to be_falsy }
- end
-
- context 'is not expired' do
- before { build.update(artifacts_expire_at: Time.now + 7.days) }
- it { is_expected.to be_truthy }
- end
- end
- end
-
- describe '#artifacts_expired?' do
- subject { build.artifacts_expired? }
-
- context 'is expired' do
- before { build.update(artifacts_expire_at: Time.now - 7.days) }
-
- it { is_expected.to be_truthy }
- end
-
- context 'is not expired' do
- before { build.update(artifacts_expire_at: Time.now + 7.days) }
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#artifacts_metadata?' do
- subject { build.artifacts_metadata? }
- context 'artifacts metadata does not exist' do
- it { is_expected.to be_falsy }
- end
-
- context 'artifacts archive is a zip file and metadata exists' do
- let(:build) { create(:ci_build, :artifacts) }
- it { is_expected.to be_truthy }
- end
- end
- describe '#repo_url' do
- let(:build) { create(:ci_build) }
- let(:project) { build.project }
-
- subject { build.repo_url }
-
- it { is_expected.to be_a(String) }
- it { is_expected.to end_with(".git") }
- it { is_expected.to start_with(project.web_url[0..6]) }
- it { is_expected.to include(build.token) }
- it { is_expected.to include('gitlab-ci-token') }
- it { is_expected.to include(project.web_url[7..-1]) }
- end
-
- describe '#artifacts_expire_in' do
- subject { build.artifacts_expire_in }
- it { is_expected.to be_nil }
-
- context 'when artifacts_expire_at is specified' do
- let(:expire_at) { Time.now + 7.days }
-
- before { build.artifacts_expire_at = expire_at }
-
- it { is_expected.to be_within(5).of(expire_at - Time.now) }
- end
- end
-
- describe '#artifacts_expire_in=' do
- subject { build.artifacts_expire_in }
-
- it 'when assigning valid duration' do
- build.artifacts_expire_in = '7 days'
-
- is_expected.to be_within(10).of(7.days.to_i)
- end
-
- it 'when assigning invalid duration' do
- expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
- is_expected.to be_nil
- end
-
- it 'when resseting value' do
- build.artifacts_expire_in = nil
-
- is_expected.to be_nil
- end
- end
-
- describe '#keep_artifacts!' do
- let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
-
- it 'to reset expire_at' do
- build.keep_artifacts!
-
- expect(build.artifacts_expire_at).to be_nil
- end
- end
-
- describe '#depends_on_builds' do
- let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
- let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
- let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
- let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
-
- it 'expects to have no dependents if this is first build' do
- expect(build.depends_on_builds).to be_empty
- end
-
- it 'expects to have one dependent if this is test' do
- expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
- end
-
- it 'expects to have all builds from build and test stage if this is last' do
- expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
- end
-
- it 'expects to have retried builds instead the original ones' do
- retried_rspec = Ci::Build.retry(rspec_test)
- expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
- end
- end
-
- def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
- create(factory, source_project_id: pipeline.gl_project_id,
- target_project_id: pipeline.gl_project_id,
- source_branch: build.ref,
- created_at: created_at)
- end
-
- describe '#merge_request' do
- context 'when a MR has a reference to the pipeline' do
- before do
- @merge_request = create_mr(build, pipeline, factory: :merge_request)
-
- commits = [double(id: pipeline.sha)]
- allow(@merge_request).to receive(:commits).and_return(commits)
- allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
- end
-
- it 'returns the single associated MR' do
- expect(build.merge_request.id).to eq(@merge_request.id)
- end
- end
-
- context 'when there is not a MR referencing the pipeline' do
- it 'returns nil' do
- expect(build.merge_request).to be_nil
- end
- end
-
- context 'when more than one MR have a reference to the pipeline' do
- before do
- @merge_request = create_mr(build, pipeline, factory: :merge_request)
- @merge_request.close!
- @merge_request2 = create_mr(build, pipeline, factory: :merge_request)
-
- commits = [double(id: pipeline.sha)]
- allow(@merge_request).to receive(:commits).and_return(commits)
- allow(@merge_request2).to receive(:commits).and_return(commits)
- allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
- end
-
- it 'returns the first MR' do
- expect(build.merge_request.id).to eq(@merge_request.id)
- end
- end
-
- context 'when a Build is created after the MR' do
- before do
- @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
- pipeline2 = create(:ci_pipeline, project: project)
- @build2 = create(:ci_build, pipeline: pipeline2)
-
- allow(@merge_request).to receive(:commits_sha).
- and_return([pipeline.sha, pipeline2.sha])
- allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
- end
-
- it 'returns the current MR' do
- expect(@build2.merge_request.id).to eq(@merge_request.id)
- end
- end
- end
-
- describe 'build erasable' do
- shared_examples 'erasable' do
- it 'removes artifact file' do
- expect(build.artifacts_file.exists?).to be_falsy
- end
-
- it 'removes artifact metadata file' do
- expect(build.artifacts_metadata.exists?).to be_falsy
- end
-
- it 'erases build trace in trace file' do
- expect(build.trace).to be_empty
- end
-
- it 'sets erased to true' do
- expect(build.erased?).to be true
- end
-
- it 'sets erase date' do
- expect(build.erased_at).not_to be_falsy
- end
- end
-
- context 'build is not erasable' do
- let!(:build) { create(:ci_build) }
-
- describe '#erase' do
- subject { build.erase }
-
- it { is_expected.to be false }
- end
-
- describe '#erasable?' do
- subject { build.erasable? }
- it { is_expected.to eq false }
- end
- end
-
- context 'build is erasable' do
- let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
-
- describe '#erase' do
- before do
- build.erase(erased_by: user)
- end
-
- context 'erased by user' do
- let!(:user) { create(:user, username: 'eraser') }
-
- include_examples 'erasable'
-
- it 'records user who erased a build' do
- expect(build.erased_by).to eq user
- end
- end
-
- context 'erased by system' do
- let(:user) { nil }
-
- include_examples 'erasable'
-
- it 'does not set user who erased a build' do
- expect(build.erased_by).to be_nil
- end
- end
- end
-
- describe '#erasable?' do
- subject { build.erasable? }
- it { is_expected.to be_truthy }
- end
-
- describe '#erased?' do
- let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
- subject { build.erased? }
-
- context 'build has not been erased' do
- it { is_expected.to be_falsey }
- end
-
- context 'build has been erased' do
- before do
- build.erase
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'metadata and build trace are not available' do
- let!(:build) { create(:ci_build, :success, :artifacts) }
-
- before do
- build.remove_artifacts_metadata!
- end
-
- describe '#erase' do
- it 'does not raise error' do
- expect { build.erase }.not_to raise_error
- end
- end
- end
- end
- end
-
- describe '#commit' do
- it 'returns commit pipeline has been created for' do
- expect(build.commit).to eq project.commit
- end
- end
-
- describe '#when' do
- subject { build.when }
-
- context 'when `when` is undefined' do
- before do
- build.when = nil
- end
-
- context 'use from gitlab-ci.yml' do
- before do
- stub_ci_pipeline_yaml_file(config)
- end
-
- context 'when config is not found' do
- let(:config) { nil }
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config does not have a questioned job' do
- let(:config) do
- YAML.dump({
- test_other: {
- script: 'Hello World'
- }
- })
- end
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config has `when`' do
- let(:config) do
- YAML.dump({
- test: {
- script: 'Hello World',
- when: 'always'
- }
- })
- end
-
- it { is_expected.to eq('always') }
- end
- end
- end
- end
-
- describe '#cancelable?' do
- subject { build }
-
- context 'when build is cancelable' do
- context 'when build is pending' do
- it { is_expected.to be_cancelable }
- end
-
- context 'when build is running' do
- before do
- build.run!
- end
-
- it { is_expected.to be_cancelable }
- end
- end
-
- context 'when build is not cancelable' do
- context 'when build is successful' do
- before do
- build.success!
- end
-
- it { is_expected.not_to be_cancelable }
- end
-
- context 'when build is failed' do
- before do
- build.drop!
- end
-
- it { is_expected.not_to be_cancelable }
- end
- end
- end
-
- describe '#retryable?' do
- subject { build }
-
- context 'when build is retryable' do
- context 'when build is successful' do
- before do
- build.success!
- end
-
- it { is_expected.to be_retryable }
- end
-
- context 'when build is failed' do
- before do
- build.drop!
- end
-
- it { is_expected.to be_retryable }
- end
-
- context 'when build is canceled' do
- before do
- build.cancel!
- end
-
- it { is_expected.to be_retryable }
- end
- end
-
- context 'when build is not retryable' do
- context 'when build is running' do
- before do
- build.run!
- end
-
- it { is_expected.not_to be_retryable }
- end
-
- context 'when build is skipped' do
- before do
- build.skip!
- end
-
- it { is_expected.not_to be_retryable }
- end
- end
- end
-
- describe '#manual?' do
- before do
- build.update(when: value)
- end
-
- subject { build.manual? }
-
- context 'when is set to manual' do
- let(:value) { 'manual' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when set to something else' do
- let(:value) { 'something else' }
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#other_actions' do
- let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
- let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
-
- subject { build.other_actions }
-
- it 'returns other actions' do
- is_expected.to contain_exactly(other_build)
- end
-
- context 'when build is retried' do
- let!(:new_build) { Ci::Build.retry(build) }
-
- it 'does not return any of them' do
- is_expected.not_to include(build, new_build)
- end
- end
-
- context 'when other build is retried' do
- let!(:retried_build) { Ci::Build.retry(other_build) }
-
- it 'returns a retried build' do
- is_expected.to contain_exactly(retried_build)
- end
- end
- end
-
- describe '#play' do
- let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
-
- subject { build.play }
-
- it 'enqueues a build' do
- is_expected.to be_pending
- is_expected.to eq(build)
- end
-
- context 'for successful build' do
- before do
- build.update(status: 'success')
- end
-
- it 'creates a new build' do
- is_expected.to be_pending
- is_expected.not_to eq(build)
- end
- end
- end
-
- describe '#when' do
- subject { build.when }
-
- context 'when `when` is undefined' do
- before do
- build.when = nil
- end
-
- context 'use from gitlab-ci.yml' do
- before do
- stub_ci_pipeline_yaml_file(config)
- end
-
- context 'when config is not found' do
- let(:config) { nil }
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config does not have a questioned job' do
- let(:config) do
- YAML.dump({
- test_other: {
- script: 'Hello World'
- }
- })
- end
-
- it { is_expected.to eq('on_success') }
- end
-
- context 'when config has when' do
- let(:config) do
- YAML.dump({
- test: {
- script: 'Hello World',
- when: 'always'
- }
- })
- end
-
- it { is_expected.to eq('always') }
- end
- end
- end
- end
-
- describe '#retryable?' do
- context 'when build is running' do
- before { build.run! }
-
- it 'returns false' do
- expect(build).not_to be_retryable
- end
- end
-
- context 'when build is finished' do
- before do
- build.success!
- end
-
- it 'returns true' do
- expect(build).to be_retryable
- end
- end
- end
-
- describe '#has_environment?' do
- subject { build.has_environment? }
-
- context 'when environment is defined' do
- before do
- build.update(environment: 'review')
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when environment is not defined' do
- before do
- build.update(environment: nil)
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#starts_environment?' do
- subject { build.starts_environment? }
-
- context 'when environment is defined' do
- before do
- build.update(environment: 'review')
- end
-
- context 'no action is defined' do
- it { is_expected.to be_truthy }
- end
-
- context 'and start action is defined' do
- before do
- build.update(options: { environment: { action: 'start' } } )
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'when environment is not defined' do
- before do
- build.update(environment: nil)
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#stops_environment?' do
- subject { build.stops_environment? }
-
- context 'when environment is defined' do
- before do
- build.update(environment: 'review')
- end
-
- context 'no action is defined' do
- it { is_expected.to be_falsey }
- end
-
- context 'and stop action is defined' do
- before do
- build.update(options: { environment: { action: 'stop' } } )
- end
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'when environment is not defined' do
- before do
- build.update(environment: nil)
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#last_deployment' do
- subject { build.last_deployment }
-
- context 'when multiple deployments are created' do
- let!(:deployment1) { create(:deployment, deployable: build) }
- let!(:deployment2) { create(:deployment, deployable: build) }
-
- it 'returns the latest one' do
- is_expected.to eq(deployment2)
- end
- end
- end
-
- describe '#outdated_deployment?' do
- subject { build.outdated_deployment? }
-
- context 'when build succeeded' do
- let(:build) { create(:ci_build, :success) }
- let!(:deployment) { create(:deployment, deployable: build) }
-
- context 'current deployment is latest' do
- it { is_expected.to be_falsey }
- end
-
- context 'current deployment is not latest on environment' do
- let!(:deployment2) { create(:deployment, environment: deployment.environment) }
-
- it { is_expected.to be_truthy }
- end
- end
-
- context 'when build failed' do
- let(:build) { create(:ci_build, :failed) }
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#expanded_environment_name' do
- subject { build.expanded_environment_name }
-
- context 'when environment uses $CI_BUILD_REF_NAME' do
- let(:build) do
- create(:ci_build,
- ref: 'master',
- environment: 'review/$CI_BUILD_REF_NAME')
- end
-
- it { is_expected.to eq('review/master') }
- end
-
- context 'when environment uses yaml_variables containing symbol keys' do
- let(:build) do
- create(:ci_build,
- yaml_variables: [{ key: :APP_HOST, value: 'host' }],
- environment: 'review/$APP_HOST')
- end
-
- it { is_expected.to eq('review/host') }
- end
- end
-
- describe '#detailed_status' do
- let(:user) { create(:user) }
-
- it 'returns a detailed status' do
- expect(build.detailed_status(user))
- .to be_a Gitlab::Ci::Status::Build::Cancelable
- end
- end
-end
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index 7e1d1126b97..af0f6a31eda 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -1,14 +1,962 @@
require 'spec_helper'
-describe Ci::Build, models: true do
- let(:build) { create(:ci_build) }
+describe Ci::Build, :models do
+ let(:project) { create(:project) }
+ let(:build) { create(:ci_build, pipeline: pipeline) }
let(:test_trace) { 'This is a test' }
+ let(:pipeline) do
+ create(:ci_pipeline, project: project,
+ sha: project.commit.id,
+ ref: project.default_branch,
+ status: 'success')
+ end
+
it { is_expected.to belong_to(:runner) }
it { is_expected.to belong_to(:trigger_request) }
it { is_expected.to belong_to(:erased_by) }
-
it { is_expected.to have_many(:deployments) }
+ it { is_expected.to validate_presence_of :ref }
+ it { is_expected.to respond_to :trace_html }
+
+ describe '#any_runners_online?' do
+ subject { build.any_runners_online? }
+
+ context 'when no runners' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when there are runners' do
+ let(:runner) { create(:ci_runner) }
+
+ before do
+ build.project.runners << runner
+ runner.update_attributes(contacted_at: 1.second.ago)
+ end
+
+ it { is_expected.to be_truthy }
+
+ it 'that is inactive' do
+ runner.update_attributes(active: false)
+ is_expected.to be_falsey
+ end
+
+ it 'that is not online' do
+ runner.update_attributes(contacted_at: nil)
+ is_expected.to be_falsey
+ end
+
+ it 'that cannot handle build' do
+ expect_any_instance_of(Ci::Runner).to receive(:can_pick?).and_return(false)
+ is_expected.to be_falsey
+ end
+ end
+ end
+
+ describe '#append_trace' do
+ subject { build.trace_html }
+
+ context 'when build.trace hides runners token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.project.update(runners_token: token)
+ build.append_trace(token, 0)
+ end
+
+ it { is_expected.not_to include(token) }
+ end
+
+ context 'when build.trace hides build token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.update(token: token)
+ build.append_trace(token, 0)
+ end
+
+ it { is_expected.not_to include(token) }
+ end
+ end
+
+ describe '#artifacts?' do
+ subject { build.artifacts? }
+
+ context 'artifacts archive does not exist' do
+ before do
+ build.update_attributes(artifacts_file: nil)
+ end
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'artifacts archive exists' do
+ let(:build) { create(:ci_build, :artifacts) }
+ it { is_expected.to be_truthy }
+
+ context 'is expired' do
+ before { build.update(artifacts_expire_at: Time.now - 7.days) }
+ it { is_expected.to be_falsy }
+ end
+
+ context 'is not expired' do
+ before { build.update(artifacts_expire_at: Time.now + 7.days) }
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ describe '#artifacts_expired?' do
+ subject { build.artifacts_expired? }
+
+ context 'is expired' do
+ before { build.update(artifacts_expire_at: Time.now - 7.days) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'is not expired' do
+ before { build.update(artifacts_expire_at: Time.now + 7.days) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#artifacts_metadata?' do
+ subject { build.artifacts_metadata? }
+ context 'artifacts metadata does not exist' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'artifacts archive is a zip file and metadata exists' do
+ let(:build) { create(:ci_build, :artifacts) }
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ describe '#artifacts_expire_in' do
+ subject { build.artifacts_expire_in }
+ it { is_expected.to be_nil }
+
+ context 'when artifacts_expire_at is specified' do
+ let(:expire_at) { Time.now + 7.days }
+
+ before { build.artifacts_expire_at = expire_at }
+
+ it { is_expected.to be_within(5).of(expire_at - Time.now) }
+ end
+ end
+
+ describe '#artifacts_expire_in=' do
+ subject { build.artifacts_expire_in }
+
+ it 'when assigning valid duration' do
+ build.artifacts_expire_in = '7 days'
+
+ is_expected.to be_within(10).of(7.days.to_i)
+ end
+
+ it 'when assigning invalid duration' do
+ expect { build.artifacts_expire_in = '7 elephants' }.to raise_error(ChronicDuration::DurationParseError)
+ is_expected.to be_nil
+ end
+
+ it 'when resseting value' do
+ build.artifacts_expire_in = nil
+
+ is_expected.to be_nil
+ end
+ end
+
+ describe '#commit' do
+ it 'returns commit pipeline has been created for' do
+ expect(build.commit).to eq project.commit
+ end
+ end
+
+ describe '#create_from' do
+ before do
+ build.status = 'success'
+ build.save
+ end
+ let(:create_from_build) { Ci::Build.create_from build }
+
+ it 'exists a pending task' do
+ expect(Ci::Build.pending.count(:all)).to eq 0
+ create_from_build
+ expect(Ci::Build.pending.count(:all)).to be > 0
+ end
+ end
+
+ describe '#depends_on_builds' do
+ let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') }
+ let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') }
+ let!(:rubocop_test) { create(:ci_build, pipeline: pipeline, name: 'rubocop', stage_idx: 1, stage: 'test') }
+ let!(:staging) { create(:ci_build, pipeline: pipeline, name: 'staging', stage_idx: 2, stage: 'deploy') }
+
+ it 'expects to have no dependents if this is first build' do
+ expect(build.depends_on_builds).to be_empty
+ end
+
+ it 'expects to have one dependent if this is test' do
+ expect(rspec_test.depends_on_builds.map(&:id)).to contain_exactly(build.id)
+ end
+
+ it 'expects to have all builds from build and test stage if this is last' do
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, rspec_test.id, rubocop_test.id)
+ end
+
+ it 'expects to have retried builds instead the original ones' do
+ retried_rspec = Ci::Build.retry(rspec_test)
+ expect(staging.depends_on_builds.map(&:id)).to contain_exactly(build.id, retried_rspec.id, rubocop_test.id)
+ end
+ end
+
+ describe '#detailed_status' do
+ let(:user) { create(:user) }
+
+ it 'returns a detailed status' do
+ expect(build.detailed_status(user))
+ .to be_a Gitlab::Ci::Status::Build::Cancelable
+ end
+ end
+
+ describe 'deployment' do
+ describe '#last_deployment' do
+ subject { build.last_deployment }
+
+ context 'when multiple deployments are created' do
+ let!(:deployment1) { create(:deployment, deployable: build) }
+ let!(:deployment2) { create(:deployment, deployable: build) }
+
+ it 'returns the latest one' do
+ is_expected.to eq(deployment2)
+ end
+ end
+ end
+
+ describe '#outdated_deployment?' do
+ subject { build.outdated_deployment? }
+
+ context 'when build succeeded' do
+ let(:build) { create(:ci_build, :success) }
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ context 'current deployment is latest' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'current deployment is not latest on environment' do
+ let!(:deployment2) { create(:deployment, environment: deployment.environment) }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when build failed' do
+ let(:build) { create(:ci_build, :failed) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe 'environment' do
+ describe '#has_environment?' do
+ subject { build.has_environment? }
+
+ context 'when environment is defined' do
+ before do
+ build.update(environment: 'review')
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when environment is not defined' do
+ before do
+ build.update(environment: nil)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#expanded_environment_name' do
+ subject { build.expanded_environment_name }
+
+ context 'when environment uses $CI_BUILD_REF_NAME' do
+ let(:build) do
+ create(:ci_build,
+ ref: 'master',
+ environment: 'review/$CI_BUILD_REF_NAME')
+ end
+
+ it { is_expected.to eq('review/master') }
+ end
+
+ context 'when environment uses yaml_variables containing symbol keys' do
+ let(:build) do
+ create(:ci_build,
+ yaml_variables: [{ key: :APP_HOST, value: 'host' }],
+ environment: 'review/$APP_HOST')
+ end
+
+ it { is_expected.to eq('review/host') }
+ end
+ end
+
+ describe '#starts_environment?' do
+ subject { build.starts_environment? }
+
+ context 'when environment is defined' do
+ before do
+ build.update(environment: 'review')
+ end
+
+ context 'no action is defined' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'and start action is defined' do
+ before do
+ build.update(options: { environment: { action: 'start' } } )
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when environment is not defined' do
+ before do
+ build.update(environment: nil)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#stops_environment?' do
+ subject { build.stops_environment? }
+
+ context 'when environment is defined' do
+ before do
+ build.update(environment: 'review')
+ end
+
+ context 'no action is defined' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and stop action is defined' do
+ before do
+ build.update(options: { environment: { action: 'stop' } } )
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when environment is not defined' do
+ before do
+ build.update(environment: nil)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe 'erasable build' do
+ shared_examples 'erasable' do
+ it 'removes artifact file' do
+ expect(build.artifacts_file.exists?).to be_falsy
+ end
+
+ it 'removes artifact metadata file' do
+ expect(build.artifacts_metadata.exists?).to be_falsy
+ end
+
+ it 'erases build trace in trace file' do
+ expect(build.trace).to be_empty
+ end
+
+ it 'sets erased to true' do
+ expect(build.erased?).to be true
+ end
+
+ it 'sets erase date' do
+ expect(build.erased_at).not_to be_falsy
+ end
+ end
+
+ context 'build is not erasable' do
+ let!(:build) { create(:ci_build) }
+
+ describe '#erase' do
+ subject { build.erase }
+
+ it { is_expected.to be false }
+ end
+
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to eq false }
+ end
+ end
+
+ context 'build is erasable' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+
+ describe '#erase' do
+ before do
+ build.erase(erased_by: user)
+ end
+
+ context 'erased by user' do
+ let!(:user) { create(:user, username: 'eraser') }
+
+ include_examples 'erasable'
+
+ it 'records user who erased a build' do
+ expect(build.erased_by).to eq user
+ end
+ end
+
+ context 'erased by system' do
+ let(:user) { nil }
+
+ include_examples 'erasable'
+
+ it 'does not set user who erased a build' do
+ expect(build.erased_by).to be_nil
+ end
+ end
+ end
+
+ describe '#erasable?' do
+ subject { build.erasable? }
+ it { is_expected.to be_truthy }
+ end
+
+ describe '#erased?' do
+ let!(:build) { create(:ci_build, :trace, :success, :artifacts) }
+ subject { build.erased? }
+
+ context 'build has not been erased' do
+ it { is_expected.to be_falsey }
+ end
+
+ context 'build has been erased' do
+ before do
+ build.erase
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'metadata and build trace are not available' do
+ let!(:build) { create(:ci_build, :success, :artifacts) }
+
+ before do
+ build.remove_artifacts_metadata!
+ end
+
+ describe '#erase' do
+ it 'does not raise error' do
+ expect { build.erase }.not_to raise_error
+ end
+ end
+ end
+ end
+ end
+
+ describe '#extract_coverage' do
+ context 'valid content & regex' do
+ subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', '\(\d+.\d+\%\) covered') }
+
+ it { is_expected.to eq(98.29) }
+ end
+
+ context 'valid content & bad regex' do
+ subject { build.extract_coverage('Coverage 1033 / 1051 LOC (98.29%) covered', 'very covered') }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'no coverage content & regex' do
+ subject { build.extract_coverage('No coverage for today :sad:', '\(\d+.\d+\%\) covered') }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'multiple results in content & regex' do
+ subject { build.extract_coverage(' (98.39%) covered. (98.29%) covered', '\(\d+.\d+\%\) covered') }
+
+ it { is_expected.to eq(98.29) }
+ end
+
+ context 'using a regex capture' do
+ subject { build.extract_coverage('TOTAL 9926 3489 65%', 'TOTAL\s+\d+\s+\d+\s+(\d{1,3}\%)') }
+
+ it { is_expected.to eq(65) }
+ end
+ end
+
+ describe '#first_pending' do
+ let!(:first) { create(:ci_build, pipeline: pipeline, status: 'pending', created_at: Date.yesterday) }
+ let!(:second) { create(:ci_build, pipeline: pipeline, status: 'pending') }
+ subject { Ci::Build.first_pending }
+
+ it { is_expected.to be_a(Ci::Build) }
+ it('returns with the first pending build') { is_expected.to eq(first) }
+ end
+
+ describe '#failed_but_allowed?' do
+ subject { build.failed_but_allowed? }
+
+ context 'when build is not allowed to fail' do
+ before do
+ build.allow_failure = false
+ end
+
+ context 'and build.status is success' do
+ before do
+ build.status = 'success'
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and build.status is failed' do
+ before do
+ build.status = 'failed'
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'when build is allowed to fail' do
+ before do
+ build.allow_failure = true
+ end
+
+ context 'and build.status is success' do
+ before do
+ build.status = 'success'
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'and build.status is failed' do
+ before do
+ build.status = 'failed'
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ describe 'flags' do
+ describe '#cancelable?' do
+ subject { build }
+
+ context 'when build is cancelable' do
+ context 'when build is pending' do
+ it { is_expected.to be_cancelable }
+ end
+
+ context 'when build is running' do
+ before do
+ build.run!
+ end
+
+ it { is_expected.to be_cancelable }
+ end
+ end
+
+ context 'when build is not cancelable' do
+ context 'when build is successful' do
+ before do
+ build.success!
+ end
+
+ it { is_expected.not_to be_cancelable }
+ end
+
+ context 'when build is failed' do
+ before do
+ build.drop!
+ end
+
+ it { is_expected.not_to be_cancelable }
+ end
+ end
+ end
+
+ describe '#retryable?' do
+ subject { build }
+
+ context 'when build is retryable' do
+ context 'when build is successful' do
+ before do
+ build.success!
+ end
+
+ it { is_expected.to be_retryable }
+ end
+
+ context 'when build is failed' do
+ before do
+ build.drop!
+ end
+
+ it { is_expected.to be_retryable }
+ end
+
+ context 'when build is canceled' do
+ before do
+ build.cancel!
+ end
+
+ it { is_expected.to be_retryable }
+ end
+ end
+
+ context 'when build is not retryable' do
+ context 'when build is running' do
+ before do
+ build.run!
+ end
+
+ it { is_expected.not_to be_retryable }
+ end
+
+ context 'when build is skipped' do
+ before do
+ build.skip!
+ end
+
+ it { is_expected.not_to be_retryable }
+ end
+ end
+ end
+
+ describe '#manual?' do
+ before do
+ build.update(when: value)
+ end
+
+ subject { build.manual? }
+
+ context 'when is set to manual' do
+ let(:value) { 'manual' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when set to something else' do
+ let(:value) { 'something else' }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe '#has_tags?' do
+ context 'when build has tags' do
+ subject { create(:ci_build, tag_list: ['tag']) }
+ it { is_expected.to have_tags }
+ end
+
+ context 'when build does not have tags' do
+ subject { create(:ci_build, tag_list: []) }
+ it { is_expected.not_to have_tags }
+ end
+ end
+
+ describe '#keep_artifacts!' do
+ let(:build) { create(:ci_build, artifacts_expire_at: Time.now + 7.days) }
+
+ it 'to reset expire_at' do
+ build.keep_artifacts!
+
+ expect(build.artifacts_expire_at).to be_nil
+ end
+ end
+
+ describe '#merge_request' do
+ def create_mr(build, pipeline, factory: :merge_request, created_at: Time.now)
+ create(factory, source_project_id: pipeline.gl_project_id,
+ target_project_id: pipeline.gl_project_id,
+ source_branch: build.ref,
+ created_at: created_at)
+ end
+
+ context 'when a MR has a reference to the pipeline' do
+ before do
+ @merge_request = create_mr(build, pipeline, factory: :merge_request)
+
+ commits = [double(id: pipeline.sha)]
+ allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+ end
+
+ it 'returns the single associated MR' do
+ expect(build.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+
+ context 'when there is not a MR referencing the pipeline' do
+ it 'returns nil' do
+ expect(build.merge_request).to be_nil
+ end
+ end
+
+ context 'when more than one MR have a reference to the pipeline' do
+ before do
+ @merge_request = create_mr(build, pipeline, factory: :merge_request)
+ @merge_request.close!
+ @merge_request2 = create_mr(build, pipeline, factory: :merge_request)
+
+ commits = [double(id: pipeline.sha)]
+ allow(@merge_request).to receive(:commits).and_return(commits)
+ allow(@merge_request2).to receive(:commits).and_return(commits)
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request, @merge_request2])
+ end
+
+ it 'returns the first MR' do
+ expect(build.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+
+ context 'when a Build is created after the MR' do
+ before do
+ @merge_request = create_mr(build, pipeline, factory: :merge_request_with_diffs)
+ pipeline2 = create(:ci_pipeline, project: project)
+ @build2 = create(:ci_build, pipeline: pipeline2)
+
+ allow(@merge_request).to receive(:commits_sha).
+ and_return([pipeline.sha, pipeline2.sha])
+ allow(MergeRequest).to receive_message_chain(:includes, :where, :reorder).and_return([@merge_request])
+ end
+
+ it 'returns the current MR' do
+ expect(@build2.merge_request.id).to eq(@merge_request.id)
+ end
+ end
+ end
+
+ describe '#options' do
+ let(:options) do
+ {
+ image: "ruby:2.1",
+ services: [
+ "postgres"
+ ]
+ }
+ end
+
+ it 'contains options' do
+ expect(build.options).to eq(options)
+ end
+ end
+
+ describe '#other_actions' do
+ let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+ let!(:other_build) { create(:ci_build, :manual, pipeline: pipeline, name: 'other action') }
+
+ subject { build.other_actions }
+
+ it 'returns other actions' do
+ is_expected.to contain_exactly(other_build)
+ end
+
+ context 'when build is retried' do
+ let!(:new_build) { Ci::Build.retry(build) }
+
+ it 'does not return any of them' do
+ is_expected.not_to include(build, new_build)
+ end
+ end
+
+ context 'when other build is retried' do
+ let!(:retried_build) { Ci::Build.retry(other_build) }
+
+ it 'returns a retried build' do
+ is_expected.to contain_exactly(retried_build)
+ end
+ end
+ end
+
+ describe '#persisted_environment' do
+ before do
+ @environment = create(:environment, project: project, name: "foo-#{project.default_branch}")
+ end
+
+ subject { build.persisted_environment }
+
+ context 'referenced literally' do
+ let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-#{project.default_branch}") }
+
+ it { is_expected.to eq(@environment) }
+ end
+
+ context 'referenced with a variable' do
+ let(:build) { create(:ci_build, pipeline: pipeline, environment: "foo-$CI_BUILD_REF_NAME") }
+
+ it { is_expected.to eq(@environment) }
+ end
+ end
+
+ describe '#play' do
+ let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+
+ subject { build.play }
+
+ it 'enqueues a build' do
+ is_expected.to be_pending
+ is_expected.to eq(build)
+ end
+
+ context 'for successful build' do
+ before do
+ build.update(status: 'success')
+ end
+
+ it 'creates a new build' do
+ is_expected.to be_pending
+ is_expected.not_to eq(build)
+ end
+ end
+ end
+
+ describe 'project settings' do
+ describe '#timeout' do
+ it 'returns project timeout configuration' do
+ expect(build.timeout).to eq(project.build_timeout)
+ end
+ end
+
+ describe '#allow_git_fetch' do
+ it 'return project allow_git_fetch configuration' do
+ expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch)
+ end
+ end
+ end
+
+ describe '#project' do
+ subject { build.project }
+
+ it { is_expected.to eq(pipeline.project) }
+ end
+
+ describe '#project_id' do
+ subject { build.project_id }
+
+ it { is_expected.to eq(pipeline.project_id) }
+ end
+
+ describe '#project_name' do
+ subject { build.project_name }
+
+ it { is_expected.to eq(project.name) }
+ end
+
+ describe '#raw_trace' do
+ subject { build.raw_trace }
+
+ context 'when build.trace hides runners token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.project.update(runners_token: token)
+ build.update(trace: token)
+ end
+
+ it { is_expected.not_to include(token) }
+ end
+
+ context 'when build.trace hides build token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.update(token: token)
+ build.update(trace: token)
+ end
+
+ it { is_expected.not_to include(token) }
+ end
+ end
+
+ describe '#ref_slug' do
+ {
+ 'master' => 'master',
+ '1-foo' => '1-foo',
+ 'fix/1-foo' => 'fix-1-foo',
+ 'fix-1-foo' => 'fix-1-foo',
+ 'a' * 63 => 'a' * 63,
+ 'a' * 64 => 'a' * 63,
+ 'FOO' => 'foo',
+ }.each do |ref, slug|
+ it "transforms #{ref} to #{slug}" do
+ build.ref = ref
+
+ expect(build.ref_slug).to eq(slug)
+ end
+ end
+ end
+
+ describe '#repo_url' do
+ let(:build) { create(:ci_build) }
+ let(:project) { build.project }
+
+ subject { build.repo_url }
+
+ it { is_expected.to be_a(String) }
+ it { is_expected.to end_with(".git") }
+ it { is_expected.to start_with(project.web_url[0..6]) }
+ it { is_expected.to include(build.token) }
+ it { is_expected.to include('gitlab-ci-token') }
+ it { is_expected.to include(project.web_url[7..-1]) }
+ end
+
+ describe '#stuck?' do
+ subject { build.stuck? }
+
+ context "when commit_status.status is pending" do
+ before do
+ build.status = 'pending'
+ end
+
+ it { is_expected.to be_truthy }
+
+ context "and there are specific runner" do
+ let(:runner) { create(:ci_runner, contacted_at: 1.second.ago) }
+
+ before do
+ build.project.runners << runner
+ runner.save
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ %w[success failed canceled running].each do |state|
+ context "when commit_status.status is #{state}" do
+ before do
+ build.status = state
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
describe '#trace' do
it 'obfuscates project runners token' do
@@ -24,6 +972,45 @@ describe Ci::Build, models: true do
expect(build.trace).to eq(test_trace)
end
+
+ context 'when build does not have trace' do
+ it 'is is empty' do
+ expect(build.trace).to be_nil
+ end
+ end
+
+ context 'when trace contains text' do
+ let(:text) { 'example output' }
+ before do
+ build.trace = text
+ end
+
+ it { expect(build.trace).to eq(text) }
+ end
+
+ context 'when trace hides runners token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.update(trace: token)
+ build.project.update(runners_token: token)
+ end
+
+ it { expect(build.trace).not_to include(token) }
+ it { expect(build.raw_trace).to include(token) }
+ end
+
+ context 'when build.trace hides build token' do
+ let(:token) { 'my_secret_token' }
+
+ before do
+ build.update(trace: token)
+ build.update(token: token)
+ end
+
+ it { expect(build.trace).not_to include(token) }
+ it { expect(build.raw_trace).to include(token) }
+ end
end
describe '#has_trace_file?' do
@@ -111,4 +1098,289 @@ describe Ci::Build, models: true do
build.destroy
end
end
+
+ describe '#when' do
+ subject { build.when }
+
+ context 'when `when` is undefined' do
+ before do
+ build.when = nil
+ end
+
+ context 'use from gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ context 'when config is not found' do
+ let(:config) { nil }
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'when config does not have a questioned job' do
+ let(:config) do
+ YAML.dump({
+ test_other: {
+ script: 'Hello World'
+ }
+ })
+ end
+
+ it { is_expected.to eq('on_success') }
+ end
+
+ context 'when config has `when`' do
+ let(:config) do
+ YAML.dump({
+ test: {
+ script: 'Hello World',
+ when: 'always'
+ }
+ })
+ end
+
+ it { is_expected.to eq('always') }
+ end
+ end
+ end
+ end
+
+ describe '#variables' do
+ let(:container_registry_enabled) { false }
+ let(:predefined_variables) do
+ [
+ { key: 'CI', value: 'true', public: true },
+ { key: 'GITLAB_CI', value: 'true', public: true },
+ { key: 'CI_BUILD_ID', value: build.id.to_s, public: true },
+ { key: 'CI_BUILD_TOKEN', value: build.token, public: false },
+ { key: 'CI_BUILD_REF', value: build.sha, public: true },
+ { key: 'CI_BUILD_BEFORE_SHA', value: build.before_sha, public: true },
+ { key: 'CI_BUILD_REF_NAME', value: 'master', public: true },
+ { key: 'CI_BUILD_REF_SLUG', value: 'master', public: true },
+ { key: 'CI_BUILD_NAME', value: 'test', public: true },
+ { key: 'CI_BUILD_STAGE', value: 'test', public: true },
+ { key: 'CI_SERVER_NAME', value: 'GitLab', public: true },
+ { key: 'CI_SERVER_VERSION', value: Gitlab::VERSION, public: true },
+ { key: 'CI_SERVER_REVISION', value: Gitlab::REVISION, public: true },
+ { key: 'CI_PROJECT_ID', value: project.id.to_s, public: true },
+ { key: 'CI_PROJECT_NAME', value: project.path, public: true },
+ { key: 'CI_PROJECT_PATH', value: project.path_with_namespace, public: true },
+ { key: 'CI_PROJECT_NAMESPACE', value: project.namespace.path, public: true },
+ { key: 'CI_PROJECT_URL', value: project.web_url, public: true },
+ { key: 'CI_PIPELINE_ID', value: pipeline.id.to_s, public: true }
+ ]
+ end
+
+ before do
+ stub_container_registry_config(enabled: container_registry_enabled, host_port: 'registry.example.com')
+ end
+
+ subject { build.variables }
+
+ context 'returns variables' do
+ before do
+ build.yaml_variables = []
+ end
+
+ it { is_expected.to eq(predefined_variables) }
+ end
+
+ context 'when build has user' do
+ let(:user) { create(:user, username: 'starter') }
+ let(:user_variables) do
+ [
+ { key: 'GITLAB_USER_ID', value: user.id.to_s, public: true },
+ { key: 'GITLAB_USER_EMAIL', value: user.email, public: true }
+ ]
+ end
+
+ before do
+ build.update_attributes(user: user)
+ end
+
+ it { user_variables.each { |v| is_expected.to include(v) } }
+ end
+
+ context 'when build has an environment' do
+ before do
+ build.update(environment: 'production')
+ create(:environment, project: build.project, name: 'production', slug: 'prod-slug')
+ end
+
+ let(:environment_variables) do
+ [
+ { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true },
+ { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true }
+ ]
+ end
+
+ it { environment_variables.each { |v| is_expected.to include(v) } }
+ end
+
+ context 'when build started manually' do
+ before do
+ build.update_attributes(when: :manual)
+ end
+
+ let(:manual_variable) do
+ { key: 'CI_BUILD_MANUAL', value: 'true', public: true }
+ end
+
+ it { is_expected.to include(manual_variable) }
+ end
+
+ context 'when build is for tag' do
+ let(:tag_variable) do
+ { key: 'CI_BUILD_TAG', value: 'master', public: true }
+ end
+
+ before do
+ build.update_attributes(tag: true)
+ end
+
+ it { is_expected.to include(tag_variable) }
+ end
+
+ context 'when secure variable is defined' do
+ let(:secure_variable) do
+ { key: 'SECRET_KEY', value: 'secret_value', public: false }
+ end
+
+ before do
+ build.project.variables << Ci::Variable.new(key: 'SECRET_KEY', value: 'secret_value')
+ end
+
+ it { is_expected.to include(secure_variable) }
+ end
+
+ context 'when build is for triggers' do
+ let(:trigger) { create(:ci_trigger, project: project) }
+ let(:trigger_request) { create(:ci_trigger_request_with_variables, pipeline: pipeline, trigger: trigger) }
+ let(:user_trigger_variable) do
+ { key: :TRIGGER_KEY_1, value: 'TRIGGER_VALUE_1', public: false }
+ end
+ let(:predefined_trigger_variable) do
+ { key: 'CI_BUILD_TRIGGERED', value: 'true', public: true }
+ end
+
+ before do
+ build.trigger_request = trigger_request
+ end
+
+ it { is_expected.to include(user_trigger_variable) }
+ it { is_expected.to include(predefined_trigger_variable) }
+ end
+
+ context 'when yaml_variables are undefined' do
+ before do
+ build.yaml_variables = nil
+ end
+
+ context 'use from gitlab-ci.yml' do
+ before do
+ stub_ci_pipeline_yaml_file(config)
+ end
+
+ context 'when config is not found' do
+ let(:config) { nil }
+
+ it { is_expected.to eq(predefined_variables) }
+ end
+
+ context 'when config does not have a questioned job' do
+ let(:config) do
+ YAML.dump({
+ test_other: {
+ script: 'Hello World'
+ }
+ })
+ end
+
+ it { is_expected.to eq(predefined_variables) }
+ end
+
+ context 'when config has variables' do
+ let(:config) do
+ YAML.dump({
+ test: {
+ script: 'Hello World',
+ variables: {
+ KEY: 'value'
+ }
+ }
+ })
+ end
+ let(:variables) do
+ [{ key: 'KEY', value: 'value', public: true }]
+ end
+
+ it { is_expected.to eq(predefined_variables + variables) }
+ end
+ end
+ end
+
+ context 'when container registry is enabled' do
+ let(:container_registry_enabled) { true }
+ let(:ci_registry) do
+ { key: 'CI_REGISTRY', value: 'registry.example.com', public: true }
+ end
+ let(:ci_registry_image) do
+ { key: 'CI_REGISTRY_IMAGE', value: project.container_registry_repository_url, public: true }
+ end
+
+ context 'and is disabled for project' do
+ before do
+ project.update(container_registry_enabled: false)
+ end
+
+ it { is_expected.to include(ci_registry) }
+ it { is_expected.not_to include(ci_registry_image) }
+ end
+
+ context 'and is enabled for project' do
+ before do
+ project.update(container_registry_enabled: true)
+ end
+
+ it { is_expected.to include(ci_registry) }
+ it { is_expected.to include(ci_registry_image) }
+ end
+ end
+
+ context 'when runner is assigned to build' do
+ let(:runner) { create(:ci_runner, description: 'description', tag_list: ['docker', 'linux']) }
+
+ before do
+ build.update(runner: runner)
+ end
+
+ it { is_expected.to include({ key: 'CI_RUNNER_ID', value: runner.id.to_s, public: true }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_DESCRIPTION', value: 'description', public: true }) }
+ it { is_expected.to include({ key: 'CI_RUNNER_TAGS', value: 'docker, linux', public: true }) }
+ end
+
+ context 'when build is for a deployment' do
+ let(:deployment_variable) { { key: 'KUBERNETES_TOKEN', value: 'TOKEN', public: false } }
+
+ before do
+ build.environment = 'production'
+ allow(project).to receive(:deployment_variables).and_return([deployment_variable])
+ end
+
+ it { is_expected.to include(deployment_variable) }
+ end
+
+ context 'returns variables in valid order' do
+ before do
+ allow(build).to receive(:predefined_variables) { ['predefined'] }
+ allow(project).to receive(:predefined_variables) { ['project'] }
+ allow(pipeline).to receive(:predefined_variables) { ['pipeline'] }
+ allow(build).to receive(:yaml_variables) { ['yaml'] }
+ allow(project).to receive(:secret_variables) { ['secret'] }
+ end
+
+ it { is_expected.to eq(%w[predefined project pipeline yaml secret]) }
+ end
+ end
end
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index cebaa157ef3..d1aee27057a 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -888,6 +888,48 @@ describe Ci::Pipeline, models: true do
end
end
+ describe '#stuck?' do
+ before do
+ create(:ci_build, :pending, pipeline: pipeline)
+ end
+
+ context 'when pipeline is stuck' do
+ it 'is stuck' do
+ expect(pipeline).to be_stuck
+ end
+ end
+
+ context 'when pipeline is not stuck' do
+ before { create(:ci_runner, :shared, :online) }
+
+ it 'is not stuck' do
+ expect(pipeline).not_to be_stuck
+ end
+ end
+ end
+
+ describe '#has_yaml_errors?' do
+ context 'when pipeline has errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, config: { rspec: nil })
+ end
+
+ it 'contains yaml errors' do
+ expect(pipeline).to have_yaml_errors
+ end
+ end
+
+ context 'when pipeline does not have errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, config: { rspec: { script: 'rake test' } })
+ end
+
+ it 'does not containyaml errors' do
+ expect(pipeline).not_to have_yaml_errors
+ end
+ end
+ end
+
describe 'notifications when pipeline success or failed' do
let(:project) { create(:project) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 701f3323c0f..64ea607eb95 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -243,4 +243,23 @@ describe CommitStatus, models: true do
.to be_a Gitlab::Ci::Status::Success
end
end
+
+ describe '#sortable_name' do
+ tests = {
+ 'karma' => ['karma'],
+ 'karma 0 20' => ['karma ', 0, ' ', 20],
+ 'karma 10 20' => ['karma ', 10, ' ', 20],
+ 'karma 50:100' => ['karma ', 50, ':', 100],
+ 'karma 1.10' => ['karma ', 1, '.', 10],
+ 'karma 1.5.1' => ['karma ', 1, '.', 5, '.', 1],
+ 'karma 1 a' => ['karma ', 1, ' a']
+ }
+
+ tests.each do |name, sortable_name|
+ it "'#{name}' sorts as '#{sortable_name}'" do
+ commit_status.name = name
+ expect(commit_status.sortable_name).to eq(sortable_name)
+ end
+ end
+ end
end
diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb
index 93eb402e060..96efe1696c3 100644
--- a/spec/models/environment_spec.rb
+++ b/spec/models/environment_spec.rb
@@ -63,6 +63,23 @@ describe Environment, models: true do
end
end
+ describe '#update_merge_request_metrics?' do
+ { 'production' => true,
+ 'production/eu' => true,
+ 'production/www.gitlab.com' => true,
+ 'productioneu' => false,
+ 'Production' => false,
+ 'Production/eu' => false,
+ 'test-production' => false
+ }.each do |name, expected_value|
+ it "returns #{expected_value} for #{name}" do
+ env = create(:environment, name: name)
+
+ expect(env.update_merge_request_metrics?).to eq(expected_value)
+ end
+ end
+ end
+
describe '#first_deployment_for' do
let(:project) { create(:project) }
let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) }
diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb
index dd033480527..d87684fd49e 100644
--- a/spec/models/global_milestone_spec.rb
+++ b/spec/models/global_milestone_spec.rb
@@ -7,26 +7,72 @@ describe GlobalMilestone, models: true do
let(:project1) { create(:project, group: group) }
let(:project2) { create(:project, path: 'gitlab-ci', group: group) }
let(:project3) { create(:project, path: 'cookbook-gitlab', group: group) }
- let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
- let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) }
- let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) }
- let(:milestone2_project1) { create(:milestone, title: "VD-123", project: project1) }
- let(:milestone2_project2) { create(:milestone, title: "VD-123", project: project2) }
- let(:milestone2_project3) { create(:milestone, title: "VD-123", project: project3) }
describe '.build_collection' do
+ let(:milestone1_due_date) { 2.weeks.from_now.to_date }
+
+ let!(:milestone1_project1) do
+ create(
+ :milestone,
+ title: "Milestone v1.2",
+ project: project1,
+ due_date: milestone1_due_date
+ )
+ end
+
+ let!(:milestone1_project2) do
+ create(
+ :milestone,
+ title: "Milestone v1.2",
+ project: project2,
+ due_date: milestone1_due_date
+ )
+ end
+
+ let!(:milestone1_project3) do
+ create(
+ :milestone,
+ title: "Milestone v1.2",
+ project: project3,
+ due_date: milestone1_due_date
+ )
+ end
+
+ let!(:milestone2_project1) do
+ create(
+ :milestone,
+ title: "VD-123",
+ project: project1,
+ due_date: nil
+ )
+ end
+
+ let!(:milestone2_project2) do
+ create(
+ :milestone,
+ title: "VD-123",
+ project: project2,
+ due_date: nil
+ )
+ end
+
+ let!(:milestone2_project3) do
+ create(
+ :milestone,
+ title: "VD-123",
+ project: project3,
+ due_date: nil
+ )
+ end
+
before do
- milestones =
- [
- milestone1_project1,
- milestone1_project2,
- milestone1_project3,
- milestone2_project1,
- milestone2_project2,
- milestone2_project3
- ]
+ projects = [
+ project1,
+ project2,
+ project3
+ ]
- @global_milestones = GlobalMilestone.build_collection(milestones)
+ @global_milestones = GlobalMilestone.build_collection(projects, {})
end
it 'has all project milestones' do
@@ -40,9 +86,17 @@ describe GlobalMilestone, models: true do
it 'has all project milestones' do
expect(@global_milestones.map { |group_milestone| group_milestone.milestones.count }.sum).to eq(6)
end
+
+ it 'sorts collection by due date' do
+ expect(@global_milestones.map(&:due_date)).to eq [nil, milestone1_due_date]
+ end
end
describe '#initialize' do
+ let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) }
+ let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) }
+ let(:milestone1_project3) { create(:milestone, title: "Milestone v1.2", project: project3) }
+
before do
milestones =
[
diff --git a/spec/models/group_milestone_spec.rb b/spec/models/group_milestone_spec.rb
new file mode 100644
index 00000000000..601167c3bd3
--- /dev/null
+++ b/spec/models/group_milestone_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe GroupMilestone, models: true do
+ let(:group) { create(:group) }
+ let(:project) { create(:project, group: group) }
+ let(:project_milestone) do
+ create(:milestone, title: "Milestone v1.2", project: project)
+ end
+
+ describe '.build' do
+ it 'returns milestone with group assigned' do
+ milestone = GroupMilestone.build(
+ group,
+ [project],
+ project_milestone.title
+ )
+
+ expect(milestone.group).to eq group
+ end
+ end
+
+ describe '.build_collection' do
+ before do
+ project_milestone
+ end
+
+ it 'returns array of milestones, each with group assigned' do
+ milestones = GroupMilestone.build_collection(group, [project], {})
+ expect(milestones).to all(have_attributes(group: group))
+ end
+ end
+end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 7758b7ffa97..5eaddd822be 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -28,6 +28,15 @@ describe Key, models: true do
expect(build(:key, user: user).publishable_key).to include("#{user.name} (#{Gitlab.config.gitlab.host})")
end
end
+
+ describe "#update_last_used_at" do
+ it "enqueues a UseKeyWorker job" do
+ key = create(:key)
+
+ expect(UseKeyWorker).to receive(:perform_async).with(key.id)
+ key.update_last_used_at
+ end
+ end
end
context "validation of uniqueness (based on fingerprint uniqueness)" do
diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb
index 0c163659a71..a9139f7d4ab 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -31,12 +31,14 @@ describe Label, models: true do
it 'validates title' do
is_expected.not_to allow_value('G,ITLAB').for(:title)
is_expected.not_to allow_value('').for(:title)
+ is_expected.not_to allow_value('s' * 256).for(:title)
is_expected.to allow_value('GITLAB').for(:title)
is_expected.to allow_value('gitlab').for(:title)
is_expected.to allow_value('G?ITLAB').for(:title)
is_expected.to allow_value('G&ITLAB').for(:title)
is_expected.to allow_value("customer's request").for(:title)
+ is_expected.to allow_value('s' * 255).for(:title)
end
end
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index d7e1a4e3b6c..497a626a418 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -1,14 +1,28 @@
require 'spec_helper'
-describe BambooService, models: true do
+describe BambooService, models: true, caching: true do
+ include ReactiveCachingHelpers
+
+ let(:bamboo_url) { 'http://gitlab.com/bamboo' }
+
+ subject(:service) do
+ described_class.create(
+ project: create(:empty_project),
+ properties: {
+ bamboo_url: bamboo_url,
+ username: 'mic',
+ password: 'password',
+ build_key: 'foo'
+ }
+ )
+ end
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
- subject { service }
-
context 'when service is active' do
before { subject.active = true }
@@ -103,90 +117,103 @@ describe BambooService, models: true do
end
describe '#build_page' do
- it 'returns a specific URL when status is 500' do
- stub_request(status: 500)
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref')
- expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo')
+ expect(service.build_page('sha', 'ref')).to eq('foo')
end
+ end
- it 'returns a specific URL when response has no results' do
- stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
+ describe '#commit_status' do
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
- expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/foo')
+ expect(service.commit_status('sha', 'ref')).to eq('foo')
end
+ end
- it 'returns a build URL when bamboo_url has no trailing slash' do
- stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
+ describe '#calculate_reactive_cache' do
+ context '#build_page' do
+ subject { service.calculate_reactive_cache('123', 'unused')[:build_page] }
- expect(service(bamboo_url: 'http://gitlab.com/bamboo').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42')
- end
+ it 'returns a specific URL when status is 500' do
+ stub_request(status: 500)
- it 'returns a build URL when bamboo_url has a trailing slash' do
- stub_request(body: %Q({"results":{"results":{"result":{"planResultKey":{"key":"42"}}}}}))
+ is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
+ end
- expect(service(bamboo_url: 'http://gitlab.com/bamboo/').build_page('123', 'unused')).to eq('http://gitlab.com/bamboo/browse/42')
- end
- end
+ it 'returns a specific URL when response has no results' do
+ stub_request(body: bamboo_response(size: 0))
- describe '#commit_status' do
- it 'sets commit status to :error when status is 500' do
- stub_request(status: 500)
+ is_expected.to eq('http://gitlab.com/bamboo/browse/foo')
+ end
- expect(service.commit_status('123', 'unused')).to eq(:error)
- end
+ it 'returns a build URL when bamboo_url has no trailing slash' do
+ stub_request(body: bamboo_response)
- it 'sets commit status to "pending" when status is 404' do
- stub_request(status: 404)
+ is_expected.to eq('http://gitlab.com/bamboo/browse/42')
+ end
- expect(service.commit_status('123', 'unused')).to eq('pending')
- end
+ context 'bamboo_url has trailing slash' do
+ let(:bamboo_url) { 'http://gitlab.com/bamboo/' }
- it 'sets commit status to "pending" when response has no results' do
- stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
+ it 'returns a build URL' do
+ stub_request(body: bamboo_response)
- expect(service.commit_status('123', 'unused')).to eq('pending')
+ is_expected.to eq('http://gitlab.com/bamboo/browse/42')
+ end
+ end
end
- it 'sets commit status to "success" when build state contains Success' do
- stub_request(build_state: 'YAY Success!')
+ context '#commit_status' do
+ subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
- expect(service.commit_status('123', 'unused')).to eq('success')
- end
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
- it 'sets commit status to "failed" when build state contains Failed' do
- stub_request(build_state: 'NO Failed!')
+ is_expected.to eq(:error)
+ end
- expect(service.commit_status('123', 'unused')).to eq('failed')
- end
+ it 'sets commit status to "pending" when status is 404' do
+ stub_request(status: 404)
- it 'sets commit status to "pending" when build state contains Pending' do
- stub_request(build_state: 'NO Pending!')
+ is_expected.to eq('pending')
+ end
- expect(service.commit_status('123', 'unused')).to eq('pending')
- end
+ it 'sets commit status to "pending" when response has no results' do
+ stub_request(body: %Q({"results":{"results":{"size":"0"}}}))
- it 'sets commit status to :error when build state is unknown' do
- stub_request(build_state: 'FOO BAR!')
+ is_expected.to eq('pending')
+ end
- expect(service.commit_status('123', 'unused')).to eq(:error)
- end
- end
+ it 'sets commit status to "success" when build state contains Success' do
+ stub_request(body: bamboo_response(build_state: 'YAY Success!'))
- def service(bamboo_url: 'http://gitlab.com/bamboo')
- described_class.create(
- project: create(:empty_project),
- properties: {
- bamboo_url: bamboo_url,
- username: 'mic',
- password: 'password',
- build_key: 'foo'
- }
- )
+ is_expected.to eq('success')
+ end
+
+ it 'sets commit status to "failed" when build state contains Failed' do
+ stub_request(body: bamboo_response(build_state: 'NO Failed!'))
+
+ is_expected.to eq('failed')
+ end
+
+ it 'sets commit status to "pending" when build state contains Pending' do
+ stub_request(body: bamboo_response(build_state: 'NO Pending!'))
+
+ is_expected.to eq('pending')
+ end
+
+ it 'sets commit status to :error when build state is unknown' do
+ stub_request(body: bamboo_response(build_state: 'FOO BAR!'))
+
+ is_expected.to eq(:error)
+ end
+ end
end
- def stub_request(status: 200, body: nil, build_state: 'success')
+ def stub_request(status: 200, body: nil)
bamboo_full_url = 'http://mic:password@gitlab.com/bamboo/rest/api/latest/result?label=123&os_authType=basic'
- body ||= %Q({"results":{"results":{"result":{"buildState":"#{build_state}"}}}})
WebMock.stub_request(:get, bamboo_full_url).to_return(
status: status,
@@ -194,4 +221,8 @@ describe BambooService, models: true do
body: body
)
end
+
+ def bamboo_response(result_key: 42, build_state: 'success', size: 1)
+ %Q({"results":{"results":{"size":"#{size}","result":{"buildState":"#{build_state}","planResultKey":{"key":"#{result_key}"}}}}})
+ end
end
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 6f65beb79d0..dbd23ff5491 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -1,6 +1,21 @@
require 'spec_helper'
-describe BuildkiteService, models: true do
+describe BuildkiteService, models: true, caching: true do
+ include ReactiveCachingHelpers
+
+ let(:project) { create(:empty_project) }
+
+ subject(:service) do
+ described_class.create(
+ project: project,
+ properties: {
+ service_hook: true,
+ project_url: 'https://buildkite.com/account-name/example-project',
+ token: 'secret-sauce-webhook-token:secret-sauce-status-token'
+ }
+ )
+ end
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
@@ -25,21 +40,12 @@ describe BuildkiteService, models: true do
describe 'commits methods' do
before do
- @project = Project.new
- allow(@project).to receive(:default_branch).and_return('default-brancho')
-
- @service = BuildkiteService.new
- allow(@service).to receive_messages(
- project: @project,
- service_hook: true,
- project_url: 'https://buildkite.com/account-name/example-project',
- token: 'secret-sauce-webhook-token:secret-sauce-status-token'
- )
+ allow(project).to receive(:default_branch).and_return('default-brancho')
end
describe '#webhook_url' do
it 'returns the webhook url' do
- expect(@service.webhook_url).to eq(
+ expect(service.webhook_url).to eq(
'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token'
)
end
@@ -47,7 +53,7 @@ describe BuildkiteService, models: true do
describe '#commit_status_path' do
it 'returns the correct status page' do
- expect(@service.commit_status_path('2ab7834c')).to eq(
+ expect(service.commit_status_path('2ab7834c')).to eq(
'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c'
)
end
@@ -55,10 +61,53 @@ describe BuildkiteService, models: true do
describe '#build_page' do
it 'returns the correct build page' do
- expect(@service.build_page('2ab7834c', nil)).to eq(
+ expect(service.build_page('2ab7834c', nil)).to eq(
'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c'
)
end
end
+
+ describe '#commit_status' do
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
+
+ expect(service.commit_status('sha', 'ref')).to eq('foo')
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ context '#commit_status' do
+ subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
+
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
+
+ is_expected.to eq(:error)
+ end
+
+ it 'sets commit status to :error when status is 404' do
+ stub_request(status: 404)
+
+ is_expected.to eq(:error)
+ end
+
+ it 'passes through build status untouched when status is 200' do
+ stub_request(body: %Q({"status":"Great Success"}))
+
+ is_expected.to eq('Great Success')
+ end
+ end
+ end
+ end
+
+ def stub_request(status: 200, body: nil)
+ body ||= %Q({"status":"success"})
+ buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
+
+ WebMock.stub_request(:get, buildkite_full_url).to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
end
end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index f13bb1e8adf..42c2ed668bc 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
-describe DroneCiService, models: true do
+describe DroneCiService, models: true, caching: true do
+ include ReactiveCachingHelpers
+
describe 'associations' do
it { is_expected.to belong_to(:project) }
it { is_expected.to have_one(:service_hook) }
@@ -33,6 +35,10 @@ describe DroneCiService, models: true do
let(:token) { 'secret' }
let(:iid) { rand(1..9999) }
+ # URL's
+ let(:build_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
+ let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
+
before(:each) do
allow(drone).to receive_messages(
project_id: project.id,
@@ -42,22 +48,66 @@ describe DroneCiService, models: true do
token: token
)
end
+
+ def stub_request(status: 200, body: nil)
+ body ||= %Q({"status":"success"})
+
+ WebMock.stub_request(:get, commit_status_path).to_return(
+ status: status,
+ headers: { 'Content-Type' => 'application/json' },
+ body: body
+ )
+ end
end
describe "service page/path methods" do
include_context :drone_ci_service
- # URL's
- let(:commit_page) { "#{drone_url}/gitlab/#{path}/redirect/commits/#{sha}?branch=#{branch}" }
- let(:merge_request_page) { "#{drone_url}/gitlab/#{path}/redirect/pulls/#{iid}" }
- let(:commit_status_path) { "#{drone_url}/gitlab/#{path}/commits/#{sha}?branch=#{branch}&access_token=#{token}" }
- let(:merge_request_status_path) { "#{drone_url}/gitlab/#{path}/pulls/#{iid}?access_token=#{token}" }
-
- it { expect(drone.build_page(sha, branch)).to eq(commit_page) }
- it { expect(drone.commit_page(sha, branch)).to eq(commit_page) }
- it { expect(drone.merge_request_page(iid, sha, branch)).to eq(merge_request_page) }
+ it { expect(drone.build_page(sha, branch)).to eq(build_page) }
it { expect(drone.commit_status_path(sha, branch)).to eq(commit_status_path) }
- it { expect(drone.merge_request_status_path(iid, sha, branch)).to eq(merge_request_status_path) }
+ end
+
+ describe '#commit_status' do
+ include_context :drone_ci_service
+
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(drone, { commit_status: 'foo' }, 'sha', 'ref')
+
+ expect(drone.commit_status('sha', 'ref')).to eq('foo')
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ include_context :drone_ci_service
+
+ context '#commit_status' do
+ subject { drone.calculate_reactive_cache(sha, branch)[:commit_status] }
+
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
+
+ is_expected.to eq(:error)
+ end
+
+ it 'sets commit status to :error when status is 404' do
+ stub_request(status: 404)
+
+ is_expected.to eq(:error)
+ end
+
+ { "killed" => :canceled,
+ "failure" => :failed,
+ "error" => :failed,
+ "success" => "success",
+ }.each do |drone_status, our_status|
+
+ it "sets commit status to #{our_status.inspect} when returned status is #{drone_status.inspect}" do
+ stub_request(body: %Q({"status":"#{drone_status}"}))
+
+ is_expected.to eq(our_status)
+ end
+ end
+ end
end
describe "execute" do
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index f7e878844dc..a1edd083aa1 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -1,14 +1,28 @@
require 'spec_helper'
-describe TeamcityService, models: true do
+describe TeamcityService, models: true, caching: true do
+ include ReactiveCachingHelpers
+
+ let(:teamcity_url) { 'http://gitlab.com/teamcity' }
+
+ subject(:service) do
+ described_class.create(
+ project: create(:empty_project),
+ properties: {
+ teamcity_url: teamcity_url,
+ username: 'mic',
+ password: 'password',
+ build_type: 'foo'
+ }
+ )
+ end
+
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
end
describe 'Validations' do
- subject { service }
-
context 'when service is active' do
before { subject.active = true }
@@ -103,73 +117,87 @@ describe TeamcityService, models: true do
end
describe '#build_page' do
- it 'returns a specific URL when status is 500' do
- stub_request(status: 500)
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(service, { build_page: 'foo' }, 'sha', 'ref')
- expect(service.build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo')
+ expect(service.build_page('sha', 'ref')).to eq('foo')
end
+ end
- it 'returns a build URL when teamcity_url has no trailing slash' do
- stub_request(body: %Q({"build":{"id":"666"}}))
+ describe '#commit_status' do
+ it 'returns the contents of the reactive cache' do
+ stub_reactive_cache(service, { commit_status: 'foo' }, 'sha', 'ref')
- expect(service(teamcity_url: 'http://gitlab.com/teamcity').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
+ expect(service.commit_status('sha', 'ref')).to eq('foo')
end
+ end
- it 'returns a build URL when teamcity_url has a trailing slash' do
- stub_request(body: %Q({"build":{"id":"666"}}))
+ describe '#calculate_reactive_cache' do
+ context 'build_page' do
+ subject { service.calculate_reactive_cache('123', 'unused')[:build_page] }
- expect(service(teamcity_url: 'http://gitlab.com/teamcity/').build_page('123', 'unused')).to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
- end
- end
+ it 'returns a specific URL when status is 500' do
+ stub_request(status: 500)
- describe '#commit_status' do
- it 'sets commit status to :error when status is 500' do
- stub_request(status: 500)
+ is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildTypeId=foo')
+ end
- expect(service.commit_status('123', 'unused')).to eq(:error)
- end
+ it 'returns a build URL when teamcity_url has no trailing slash' do
+ stub_request(body: %Q({"build":{"id":"666"}}))
- it 'sets commit status to "pending" when status is 404' do
- stub_request(status: 404)
+ is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
+ end
- expect(service.commit_status('123', 'unused')).to eq('pending')
- end
+ context 'teamcity_url has trailing slash' do
+ let(:teamcity_url) { 'http://gitlab.com/teamcity/' }
- it 'sets commit status to "success" when build status contains SUCCESS' do
- stub_request(build_status: 'YAY SUCCESS!')
+ it 'returns a build URL' do
+ stub_request(body: %Q({"build":{"id":"666"}}))
- expect(service.commit_status('123', 'unused')).to eq('success')
+ is_expected.to eq('http://gitlab.com/teamcity/viewLog.html?buildId=666&buildTypeId=foo')
+ end
+ end
end
- it 'sets commit status to "failed" when build status contains FAILURE' do
- stub_request(build_status: 'NO FAILURE!')
+ context 'commit_status' do
+ subject { service.calculate_reactive_cache('123', 'unused')[:commit_status] }
- expect(service.commit_status('123', 'unused')).to eq('failed')
- end
+ it 'sets commit status to :error when status is 500' do
+ stub_request(status: 500)
- it 'sets commit status to "pending" when build status contains Pending' do
- stub_request(build_status: 'NO Pending!')
+ is_expected.to eq(:error)
+ end
- expect(service.commit_status('123', 'unused')).to eq('pending')
- end
+ it 'sets commit status to "pending" when status is 404' do
+ stub_request(status: 404)
- it 'sets commit status to :error when build status is unknown' do
- stub_request(build_status: 'FOO BAR!')
+ is_expected.to eq('pending')
+ end
- expect(service.commit_status('123', 'unused')).to eq(:error)
- end
- end
+ it 'sets commit status to "success" when build status contains SUCCESS' do
+ stub_request(build_status: 'YAY SUCCESS!')
- def service(teamcity_url: 'http://gitlab.com/teamcity')
- described_class.create(
- project: create(:empty_project),
- properties: {
- teamcity_url: teamcity_url,
- username: 'mic',
- password: 'password',
- build_type: 'foo'
- }
- )
+ is_expected.to eq('success')
+ end
+
+ it 'sets commit status to "failed" when build status contains FAILURE' do
+ stub_request(build_status: 'NO FAILURE!')
+
+ is_expected.to eq('failed')
+ end
+
+ it 'sets commit status to "pending" when build status contains Pending' do
+ stub_request(build_status: 'NO Pending!')
+
+ is_expected.to eq('pending')
+ end
+
+ it 'sets commit status to :error when build status is unknown' do
+ stub_request(build_status: 'FOO BAR!')
+
+ is_expected.to eq(:error)
+ end
+ end
end
def stub_request(status: 200, body: nil, build_status: 'success')
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 3ec7bb46686..e93a4e62244 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -190,34 +190,54 @@ describe Project, models: true do
end
it 'does not allow an invalid URI as import_url' do
- project2 = build(:project, import_url: 'invalid://')
+ project2 = build(:empty_project, import_url: 'invalid://')
expect(project2).not_to be_valid
end
it 'does allow a valid URI as import_url' do
- project2 = build(:project, import_url: 'ssh://test@gitlab.com/project.git')
+ project2 = build(:empty_project, import_url: 'ssh://test@gitlab.com/project.git')
expect(project2).to be_valid
end
it 'allows an empty URI' do
- project2 = build(:project, import_url: '')
+ project2 = build(:empty_project, import_url: '')
expect(project2).to be_valid
end
it 'does not produce import data on an empty URI' do
- project2 = build(:project, import_url: '')
+ project2 = build(:empty_project, import_url: '')
expect(project2.import_data).to be_nil
end
it 'does not produce import data on an invalid URI' do
- project2 = build(:project, import_url: 'test://')
+ project2 = build(:empty_project, import_url: 'test://')
expect(project2.import_data).to be_nil
end
+
+ describe 'project pending deletion' do
+ let!(:project_pending_deletion) do
+ create(:empty_project,
+ pending_delete: true)
+ end
+ let(:new_project) do
+ build(:empty_project,
+ name: project_pending_deletion.name,
+ namespace: project_pending_deletion.namespace)
+ end
+
+ before do
+ new_project.validate
+ end
+
+ it 'contains errors related to the project being deleted' do
+ expect(new_project.errors.full_messages.first).to eq('The project is still being deleted. Please try again later.')
+ end
+ end
end
describe 'default_scope' do
@@ -1525,11 +1545,13 @@ describe Project, models: true do
end
end
- describe 'change_head' do
+ describe '#change_head' do
let(:project) { create(:project) }
- it 'calls the before_change_head method' do
+ it 'calls the before_change_head and after_change_head methods' do
expect(project.repository).to receive(:before_change_head)
+ expect(project.repository).to receive(:after_change_head)
+
project.change_head(project.default_branch)
end
@@ -1545,11 +1567,6 @@ describe Project, models: true do
project.change_head(project.default_branch)
end
- it 'expires the avatar cache' do
- expect(project.repository).to receive(:expire_avatar_cache)
- project.change_head(project.default_branch)
- end
-
it 'reloads the default branch' do
expect(project).to receive(:reload_default_branch)
project.change_head(project.default_branch)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index af7e89eae05..99ca53938c8 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1150,6 +1150,24 @@ describe Repository, models: true do
end
end
+ describe '#after_change_head' do
+ it 'flushes the readme cache' do
+ expect(repository).to receive(:expire_method_caches).with([
+ :readme,
+ :changelog,
+ :license,
+ :contributing,
+ :version,
+ :gitignore,
+ :koding,
+ :gitlab_ci,
+ :avatar
+ ])
+
+ repository.after_change_head
+ end
+ end
+
describe '#before_push_tag' do
it 'flushes the cache' do
expect(repository).to receive(:expire_statistics_caches)
@@ -1513,14 +1531,6 @@ describe Repository, models: true do
end
end
- describe '#expire_avatar_cache' do
- it 'expires the cache' do
- expect(repository).to receive(:expire_method_caches).with(%i(avatar))
-
- repository.expire_avatar_cache
- end
- end
-
describe '#file_on_head' do
context 'with a non-existing repository' do
it 'returns nil' do
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index a786dc9edb3..12dd4bd83f7 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -50,6 +50,8 @@ describe API::Issues, api: true do
end
let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) }
+ let(:no_milestone_title) { URI.escape(Milestone::None.title) }
+
before do
project.team << [user, :reporter]
project.team << [guest, :guest]
@@ -107,6 +109,7 @@ describe API::Issues, api: true do
it 'returns an array of labeled issues when at least one label matches' do
get api("/issues?labels=#{label.title},foo,bar", user)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -136,6 +139,51 @@ describe API::Issues, api: true do
expect(json_response.length).to eq(0)
end
+ it 'returns an empty array if no issue matches milestone' do
+ get api("/issues?milestone=#{empty_milestone.title}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api("/issues?milestone=foo", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of issues in given milestone' do
+ get api("/issues?milestone=#{milestone.title}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(2)
+ expect(json_response.first['id']).to eq(issue.id)
+ expect(json_response.second['id']).to eq(closed_issue.id)
+ end
+
+ it 'returns an array of issues matching state in milestone' do
+ get api("/issues?milestone=#{milestone.title}"\
+ '&state=closed', user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(closed_issue.id)
+ end
+
+ it 'returns an array of issues with no milestone' do
+ get api("/issues?milestone=#{no_milestone_title}", author)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(confidential_issue.id)
+ end
+
it 'sorts by created_at descending by default' do
get api('/issues', user)
response_dates = json_response.map { |issue| issue['created_at'] }
@@ -318,6 +366,15 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(group_closed_issue.id)
end
+ it 'returns an array of issues with no milestone' do
+ get api("#{base_url}?milestone=#{no_milestone_title}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(group_confidential_issue.id)
+ end
+
it 'sorts by created_at descending by default' do
get api(base_url, user)
response_dates = json_response.map { |issue| issue['created_at'] }
@@ -357,7 +414,6 @@ describe API::Issues, api: true do
describe "GET /projects/:id/issues" do
let(:base_url) { "/projects/#{project.id}" }
- let(:title) { milestone.title }
it "returns 404 on private projects for other users" do
private_project = create(:empty_project, :private)
@@ -433,8 +489,9 @@ describe API::Issues, api: true do
expect(json_response.first['labels']).to eq([label.title])
end
- it 'returns an array of labeled project issues when at least one label matches' do
+ it 'returns an array of labeled project issues where all labels match' do
get api("#{base_url}/issues?labels=#{label.title},foo,bar", user)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(1)
@@ -463,7 +520,8 @@ describe API::Issues, api: true do
end
it 'returns an array of issues in given milestone' do
- get api("#{base_url}/issues?milestone=#{title}", user)
+ get api("#{base_url}/issues?milestone=#{milestone.title}", user)
+
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.length).to eq(2)
@@ -480,6 +538,15 @@ describe API::Issues, api: true do
expect(json_response.first['id']).to eq(closed_issue.id)
end
+ it 'returns an array of issues with no milestone' do
+ get api("#{base_url}/issues?milestone=#{no_milestone_title}", user)
+
+ expect(response).to have_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(confidential_issue.id)
+ end
+
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
response_dates = json_response.map { |issue| issue['created_at'] }
@@ -547,12 +614,21 @@ describe API::Issues, api: true do
it 'returns a project issue by iid' do
get api("/projects/#{project.id}/issues?iid=#{issue.iid}", user)
+
expect(response.status).to eq 200
+ expect(json_response.length).to eq 1
expect(json_response.first['title']).to eq issue.title
expect(json_response.first['id']).to eq issue.id
expect(json_response.first['iid']).to eq issue.iid
end
+ it 'returns an empty array for an unknown project issue iid' do
+ get api("/projects/#{project.id}/issues?iid=#{issue.iid + 10}", user)
+
+ expect(response.status).to eq 200
+ expect(json_response.length).to eq 0
+ end
+
it "returns 404 if issue id not found" do
get api("/projects/#{project.id}/issues/54321", user)
expect(response).to have_http_status(404)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index f5788d15f93..cdb16b4c46b 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -1085,7 +1085,7 @@ describe API::Projects, api: true do
end
describe 'GET /projects/search/:query' do
- let!(:query) { 'query'}
+ let!(:query) { 'query'}
let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) }
let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) }
let!(:post) { create(:empty_project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
@@ -1095,32 +1095,37 @@ describe API::Projects, api: true do
let!(:unfound_internal) { create(:empty_project, :internal, name: 'unfound internal') }
let!(:public) { create(:empty_project, :public, name: "public #{query}") }
let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') }
+ let!(:one_dot_two) { create(:empty_project, :public, name: "one.dot.two") }
shared_examples_for 'project search response' do |args = {}|
it 'returns project search responses' do
- get api("/projects/search/#{query}", current_user)
+ get api("/projects/search/#{args[:query]}", current_user)
expect(response).to have_http_status(200)
expect(json_response).to be_an Array
expect(json_response.size).to eq(args[:results])
- json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*query.*/) }
+ json_response.each { |project| expect(project['name']).to match(args[:match_regex] || /.*#{args[:query]}.*/) }
end
end
context 'when unauthenticated' do
- it_behaves_like 'project search response', results: 1 do
+ it_behaves_like 'project search response', query: 'query', results: 1 do
let(:current_user) { nil }
end
end
context 'when authenticated' do
- it_behaves_like 'project search response', results: 6 do
+ it_behaves_like 'project search response', query: 'query', results: 6 do
let(:current_user) { user }
end
+ it_behaves_like 'project search response', query: 'one.dot.two', results: 1 do
+ let(:current_user) { user }
+ end
+
end
context 'when authenticated as a different user' do
- it_behaves_like 'project search response', results: 2, match_regex: /(internal|public) query/ do
+ it_behaves_like 'project search response', query: 'query', results: 2, match_regex: /(internal|public) query/ do
let(:current_user) { user2 }
end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index ad9d8a25af4..91e3c333a02 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -16,6 +16,8 @@ describe API::Settings, 'Settings', api: true do
expect(json_response['repository_storage']).to eq('default')
expect(json_response['koding_enabled']).to be_falsey
expect(json_response['koding_url']).to be_nil
+ expect(json_response['plantuml_enabled']).to be_falsey
+ expect(json_response['plantuml_url']).to be_nil
end
end
@@ -28,7 +30,8 @@ describe API::Settings, 'Settings', api: true do
it "updates application settings" do
put api("/application/settings", admin),
- default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com'
+ default_projects_limit: 3, signin_enabled: false, repository_storage: 'custom', koding_enabled: true, koding_url: 'http://koding.example.com',
+ plantuml_enabled: true, plantuml_url: 'http://plantuml.example.com'
expect(response).to have_http_status(200)
expect(json_response['default_projects_limit']).to eq(3)
expect(json_response['signin_enabled']).to be_falsey
@@ -36,6 +39,8 @@ describe API::Settings, 'Settings', api: true do
expect(json_response['repository_storages']).to eq(['custom'])
expect(json_response['koding_enabled']).to be_truthy
expect(json_response['koding_url']).to eq('http://koding.example.com')
+ expect(json_response['plantuml_enabled']).to be_truthy
+ expect(json_response['plantuml_url']).to eq('http://plantuml.example.com')
end
end
@@ -47,5 +52,14 @@ describe API::Settings, 'Settings', api: true do
expect(json_response['error']).to eq('koding_url is missing')
end
end
+
+ context "missing plantuml_url value when plantuml_enabled is true" do
+ it "returns a blank parameter error message" do
+ put api("/application/settings", admin), plantuml_enabled: true
+
+ expect(response).to have_http_status(400)
+ expect(json_response['error']).to eq('plantuml_url is missing')
+ end
+ end
end
end
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 45b7988a054..5bf5bf0739e 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -137,6 +137,15 @@ describe API::Users, api: true do
expect(new_user.can_create_group).to eq(true)
end
+ it "creates user with optional attributes" do
+ optional_attributes = { confirm: true }
+ attributes = attributes_for(:user).merge(optional_attributes)
+
+ post api('/users', admin), attributes
+
+ expect(response).to have_http_status(201)
+ end
+
it "creates non-admin user" do
post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false)
expect(response).to have_http_status(201)
@@ -265,6 +274,14 @@ describe API::Users, api: true do
expect(response).to have_http_status(409)
expect(json_response['message']).to eq('Username has already been taken')
end
+
+ it 'creates user with new identity' do
+ post api("/users", admin), attributes_for(:user, provider: 'github', extern_uid: '67890')
+
+ expect(response).to have_http_status(201)
+ expect(json_response['identities'].first['extern_uid']).to eq('67890')
+ expect(json_response['identities'].first['provider']).to eq('github')
+ end
end
end
diff --git a/spec/serializers/build_action_entity_spec.rb b/spec/serializers/build_action_entity_spec.rb
new file mode 100644
index 00000000000..0f7be8b2c39
--- /dev/null
+++ b/spec/serializers/build_action_entity_spec.rb
@@ -0,0 +1,21 @@
+require 'spec_helper'
+
+describe BuildActionEntity do
+ let(:build) { create(:ci_build, name: 'test_build') }
+
+ let(:entity) do
+ described_class.new(build, request: double)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains original build name' do
+ expect(subject[:name]).to eq 'test_build'
+ end
+
+ it 'contains path to the action play' do
+ expect(subject[:path]).to include "builds/#{build.id}/play"
+ end
+ end
+end
diff --git a/spec/serializers/build_artifact_entity_spec.rb b/spec/serializers/build_artifact_entity_spec.rb
new file mode 100644
index 00000000000..2fc60aa9de6
--- /dev/null
+++ b/spec/serializers/build_artifact_entity_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe BuildArtifactEntity do
+ let(:build) { create(:ci_build, name: 'test:build') }
+
+ let(:entity) do
+ described_class.new(build, request: double)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains build name' do
+ expect(subject[:name]).to eq 'test:build'
+ end
+
+ it 'contains path to the artifacts' do
+ expect(subject[:path])
+ .to include "builds/#{build.id}/artifacts/download"
+ end
+ end
+end
diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb
index 15f11ac3df9..0333d73b5b5 100644
--- a/spec/serializers/commit_entity_spec.rb
+++ b/spec/serializers/commit_entity_spec.rb
@@ -33,10 +33,12 @@ describe CommitEntity do
it 'contains path to commit' do
expect(subject).to include(:commit_path)
+ expect(subject[:commit_path]).to include "commit/#{commit.id}"
end
it 'contains URL to commit' do
expect(subject).to include(:commit_url)
+ expect(subject[:commit_path]).to include "commit/#{commit.id}"
end
it 'needs to receive project in the request' do
@@ -45,4 +47,8 @@ describe CommitEntity do
subject
end
+
+ it 'exposes gravatar url that belongs to author' do
+ expect(subject.fetch(:author_gravatar_url)).to match /gravatar/
+ end
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
new file mode 100644
index 00000000000..b19464c7117
--- /dev/null
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -0,0 +1,138 @@
+require 'spec_helper'
+
+describe PipelineEntity do
+ let(:user) { create(:user) }
+ let(:request) { double('request') }
+
+ before do
+ allow(request).to receive(:user).and_return(user)
+ end
+
+ let(:entity) do
+ described_class.represent(pipeline, request: request)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ context 'when pipeline is empty' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'contains required fields' do
+ expect(subject).to include :id, :user, :path
+ expect(subject).to include :ref, :commit
+ expect(subject).to include :updated_at, :created_at
+ end
+
+ it 'contains details' do
+ expect(subject).to include :details
+ expect(subject[:details])
+ .to include :duration, :finished_at
+ expect(subject[:details])
+ .to include :stages, :artifacts, :manual_actions
+ expect(subject[:details][:status]).to include :icon, :text, :label
+ end
+
+ it 'contains flags' do
+ expect(subject).to include :flags
+ expect(subject[:flags])
+ .to include :latest, :triggered, :stuck,
+ :yaml_errors, :retryable, :cancelable
+ end
+ end
+
+ context 'when pipeline is retryable' do
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, status: :success, project: project)
+ end
+
+ before do
+ create(:ci_build, :failed, pipeline: pipeline)
+ end
+
+ context 'user has ability to retry pipeline' do
+ before { project.team << [user, :developer] }
+
+ it 'retryable flag is true' do
+ expect(subject[:flags][:retryable]).to eq true
+ end
+
+ it 'contains retry path' do
+ expect(subject[:retry_path]).to be_present
+ end
+ end
+
+ context 'user does not have ability to retry pipeline' do
+ it 'retryable flag is false' do
+ expect(subject[:flags][:retryable]).to eq false
+ end
+
+ it 'does not contain retry path' do
+ expect(subject).not_to have_key(:retry_path)
+ end
+ end
+ end
+
+ context 'when pipeline is cancelable' do
+ let(:project) { create(:empty_project) }
+
+ let(:pipeline) do
+ create(:ci_pipeline, status: :running, project: project)
+ end
+
+ before do
+ create(:ci_build, :pending, pipeline: pipeline)
+ end
+
+ context 'user has ability to cancel pipeline' do
+ before { project.team << [user, :developer] }
+
+ it 'cancelable flag is true' do
+ expect(subject[:flags][:cancelable]).to eq true
+ end
+
+ it 'contains cancel path' do
+ expect(subject[:cancel_path]).to be_present
+ end
+ end
+
+ context 'user does not have ability to cancel pipeline' do
+ it 'cancelable flag is false' do
+ expect(subject[:flags][:cancelable]).to eq false
+ end
+
+ it 'does not contain cancel path' do
+ expect(subject).not_to have_key(:cancel_path)
+ end
+ end
+ end
+
+ context 'when pipeline has YAML errors' do
+ let(:pipeline) do
+ create(:ci_pipeline, config: { rspec: { invalid: :value } })
+ end
+
+ it 'contains flag that indicates there are errors' do
+ expect(subject[:flags][:yaml_errors]).to be true
+ end
+
+ it 'contains information about error' do
+ expect(subject[:yaml_errors]).to be_present
+ end
+ end
+
+ context 'when pipeline does not have YAML errors' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
+ it 'contains flag that indicates there are no errors' do
+ expect(subject[:flags][:yaml_errors]).to be false
+ end
+
+ it 'does not contain field that normally holds an error' do
+ expect(subject).not_to have_key(:yaml_errors)
+ end
+ end
+ end
+end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
new file mode 100644
index 00000000000..3a32cb394dd
--- /dev/null
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -0,0 +1,101 @@
+require 'spec_helper'
+
+describe PipelineSerializer do
+ let(:user) { create(:user) }
+
+ let(:serializer) do
+ described_class.new(user: user)
+ end
+
+ let(:entity) do
+ serializer.represent(resource)
+ end
+
+ subject { entity.as_json }
+
+ describe '#represent' do
+ context 'when used without pagination' do
+ it 'created a not paginated serializer' do
+ expect(serializer).not_to be_paginated
+ end
+
+ context 'when a single object is being serialized' do
+ let(:resource) { create(:ci_empty_pipeline) }
+
+ it 'serializers the pipeline object' do
+ expect(subject[:id]).to eq resource.id
+ end
+ end
+
+ context 'when multiple objects are being serialized' do
+ let(:resource) { create_list(:ci_pipeline, 2) }
+
+ it 'serializers the array of pipelines' do
+ expect(subject).not_to be_empty
+ end
+ end
+ end
+
+ context 'when used with pagination' do
+ let(:request) { spy('request') }
+ let(:response) { spy('response') }
+ let(:pagination) { {} }
+
+ before do
+ allow(request)
+ .to receive(:query_parameters)
+ .and_return(pagination)
+ end
+
+ let(:serializer) do
+ described_class.new(user: user)
+ .with_pagination(request, response)
+ end
+
+ it 'created a paginated serializer' do
+ expect(serializer).to be_paginated
+ end
+
+ context 'when resource does is not paginatable' do
+ context 'when a single pipeline object is being serialized' do
+ let(:resource) { create(:ci_empty_pipeline) }
+ let(:pagination) { { page: 1, per_page: 1 } }
+
+ it 'raises error' do
+ expect { subject }
+ .to raise_error(PipelineSerializer::InvalidResourceError)
+ end
+ end
+ end
+
+ context 'when resource is paginatable relation' do
+ let(:resource) { Ci::Pipeline.all }
+ let(:pagination) { { page: 1, per_page: 2 } }
+
+ context 'when a single pipeline object is present in relation' do
+ before { create(:ci_empty_pipeline) }
+
+ it 'serializes pipeline relation' do
+ expect(subject.first).to have_key :id
+ end
+ end
+
+ context 'when a multiple pipeline objects are being serialized' do
+ before { create_list(:ci_empty_pipeline, 3) }
+
+ it 'serializes appropriate number of objects' do
+ expect(subject.count).to be 2
+ end
+
+ it 'appends relevant headers' do
+ expect(response).to receive(:[]=).with('X-Total', '3')
+ expect(response).to receive(:[]=).with('X-Total-Pages', '2')
+ expect(response).to receive(:[]=).with('X-Per-Page', '2')
+
+ subject
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/spec/serializers/request_aware_entity_spec.rb b/spec/serializers/request_aware_entity_spec.rb
new file mode 100644
index 00000000000..aa666b961dc
--- /dev/null
+++ b/spec/serializers/request_aware_entity_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe RequestAwareEntity do
+ subject do
+ Class.new.include(described_class).new
+ end
+
+ it 'includes URL helpers' do
+ expect(subject).to respond_to(:namespace_project_path)
+ end
+
+ it 'includes method for checking abilities' do
+ expect(subject).to respond_to(:can?)
+ end
+
+ it 'fetches request from options' do
+ expect(subject).to receive(:options)
+ .and_return({ request: 'some value' })
+
+ expect(subject.request).to eq 'some value'
+ end
+end
diff --git a/spec/serializers/stage_entity_spec.rb b/spec/serializers/stage_entity_spec.rb
new file mode 100644
index 00000000000..4ab40d08432
--- /dev/null
+++ b/spec/serializers/stage_entity_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe StageEntity do
+ let(:pipeline) { create(:ci_pipeline) }
+ let(:request) { double('request') }
+ let(:user) { create(:user) }
+
+ let(:entity) do
+ described_class.new(stage, request: request)
+ end
+
+ let(:stage) do
+ build(:ci_stage, pipeline: pipeline, name: 'test')
+ end
+
+ before do
+ allow(request).to receive(:user).and_return(user)
+ create(:ci_build, :success, pipeline: pipeline)
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains relevant fields' do
+ expect(subject).to include :name, :status, :path
+ end
+
+ it 'contains detailed status' do
+ expect(subject[:status]).to include :text, :label, :group, :icon
+ expect(subject[:status][:label]).to eq 'passed'
+ end
+
+ it 'contains valid name' do
+ expect(subject[:name]).to eq 'test'
+ end
+
+ it 'contains path to the stage' do
+ expect(subject[:path])
+ .to include "pipelines/#{pipeline.id}##{stage.name}"
+ end
+
+ it 'contains path to the stage dropdown' do
+ expect(subject[:dropdown_path])
+ .to include "pipelines/#{pipeline.id}/stage.json?stage=test"
+ end
+
+ it 'contains stage title' do
+ expect(subject[:title]).to eq 'test: passed'
+ end
+ end
+end
diff --git a/spec/serializers/status_entity_spec.rb b/spec/serializers/status_entity_spec.rb
new file mode 100644
index 00000000000..89428b4216e
--- /dev/null
+++ b/spec/serializers/status_entity_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe StatusEntity do
+ let(:entity) { described_class.new(status) }
+
+ let(:status) do
+ Gitlab::Ci::Status::Success.new(double('object'), double('user'))
+ end
+
+ before do
+ allow(status).to receive(:has_details?).and_return(true)
+ allow(status).to receive(:details_path).and_return('some/path')
+ end
+
+ describe '#as_json' do
+ subject { entity.as_json }
+
+ it 'contains status details' do
+ expect(subject).to include :text, :icon, :label, :group
+ expect(subject).to include :has_details, :details_path
+ end
+ end
+end
diff --git a/spec/services/projects/participants_service_spec.rb b/spec/services/projects/participants_service_spec.rb
new file mode 100644
index 00000000000..063b3bd76eb
--- /dev/null
+++ b/spec/services/projects/participants_service_spec.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe Projects::ParticipantsService, services: true do
+ describe '#groups' do
+ describe 'avatar_url' do
+ let(:project) { create(:empty_project, :public) }
+ let(:group) { create(:group, avatar: fixture_file_upload(Rails.root + 'spec/fixtures/dk.png')) }
+ let(:user) { create(:user) }
+ let(:base_url) { Settings.send(:build_base_gitlab_url) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+
+ it 'should return an url for the avatar' do
+ participants = described_class.new(project, user)
+ groups = participants.groups
+
+ expect(groups.size).to eq 1
+ expect(groups.first[:avatar_url]).to eq "#{base_url}/uploads/group/avatar/#{group.id}/dk.png"
+ end
+
+ it 'should return an url for the avatar with relative url' do
+ stub_config_setting(relative_url_root: '/gitlab')
+ stub_config_setting(url: Settings.send(:build_gitlab_url))
+
+ participants = described_class.new(project, user)
+ groups = participants.groups
+
+ expect(groups.size).to eq 1
+ expect(groups.first[:avatar_url]).to eq "#{base_url}/gitlab/uploads/group/avatar/#{group.id}/dk.png"
+ end
+ end
+ end
+end
diff --git a/spec/services/users/refresh_authorized_projects_service_spec.rb b/spec/services/users/refresh_authorized_projects_service_spec.rb
index 1f6919151de..9fbb61565e3 100644
--- a/spec/services/users/refresh_authorized_projects_service_spec.rb
+++ b/spec/services/users/refresh_authorized_projects_service_spec.rb
@@ -20,7 +20,7 @@ describe Users::RefreshAuthorizedProjectsService do
to_remove = create_authorization(project2, user)
expect(service).to receive(:update_with_lease).
- with([to_remove.id], [[user.id, project.id, Gitlab::Access::MASTER]])
+ with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
service.execute
end
@@ -29,7 +29,7 @@ describe Users::RefreshAuthorizedProjectsService do
to_remove = create_authorization(project, user, Gitlab::Access::DEVELOPER)
expect(service).to receive(:update_with_lease).
- with([to_remove.id], [[user.id, project.id, Gitlab::Access::MASTER]])
+ with([to_remove.project_id], [[user.id, project.id, Gitlab::Access::MASTER]])
service.execute
end
@@ -90,7 +90,7 @@ describe Users::RefreshAuthorizedProjectsService do
it 'removes authorizations that should be removed' do
authorization = create_authorization(project, user)
- service.update_authorizations([authorization.id])
+ service.update_authorizations([authorization.project_id])
expect(user.project_authorizations).to be_empty
end
@@ -147,7 +147,12 @@ describe Users::RefreshAuthorizedProjectsService do
end
it 'sets the values to the project authorization rows' do
- expect(hash.values).to eq([ProjectAuthorization.first])
+ expect(hash.values.length).to eq(1)
+
+ value = hash.values[0]
+
+ expect(value.project_id).to eq(project.id)
+ expect(value.access_level).to eq(Gitlab::Access::MASTER)
end
end
@@ -167,10 +172,6 @@ describe Users::RefreshAuthorizedProjectsService do
expect(service.current_authorizations.length).to eq(1)
end
- it 'includes the row ID for every row' do
- expect(row.id).to be_a_kind_of(Numeric)
- end
-
it 'includes the project ID for every row' do
expect(row.project_id).to eq(project.id)
end
diff --git a/spec/support/javascript_fixtures_helpers.rb b/spec/support/javascript_fixtures_helpers.rb
index 99e98eebdb4..0b8729db0f9 100644
--- a/spec/support/javascript_fixtures_helpers.rb
+++ b/spec/support/javascript_fixtures_helpers.rb
@@ -20,12 +20,26 @@ module JavaScriptFixturesHelpers
# Public: Store a response object as fixture file
#
- # response - response object to store
+ # response - string or response object to store
# fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATH)
#
def store_frontend_fixture(response, fixture_file_name)
fixture_file_name = File.expand_path(fixture_file_name, FIXTURE_PATH)
+ fixture = response.respond_to?(:body) ? parse_response(response) : response
+
+ FileUtils.mkdir_p(File.dirname(fixture_file_name))
+ File.write(fixture_file_name, fixture)
+ end
+
+ private
+
+ # Private: Prepare a response object for use as a frontend fixture
+ #
+ # response - response object to prepare
+ #
+ def parse_response(response)
fixture = response.body
+ fixture.force_encoding("utf-8")
response_mime_type = Mime::Type.lookup(response.content_type)
if response_mime_type.html?
@@ -34,7 +48,7 @@ module JavaScriptFixturesHelpers
link_tags = doc.css('link')
link_tags.remove
- scripts = doc.css('script')
+ scripts = doc.css("script:not([type='text/template'])")
scripts.remove
fixture = doc.to_html
@@ -44,7 +58,6 @@ module JavaScriptFixturesHelpers
fixture.gsub!(%r{="/}, "=\"http://#{test_host}/")
end
- FileUtils.mkdir_p(File.dirname(fixture_file_name))
- File.write(fixture_file_name, fixture)
+ fixture
end
end
diff --git a/spec/support/reactive_caching_helpers.rb b/spec/support/reactive_caching_helpers.rb
index 279db3c5748..98eb57f8b54 100644
--- a/spec/support/reactive_caching_helpers.rb
+++ b/spec/support/reactive_caching_helpers.rb
@@ -3,31 +3,35 @@ module ReactiveCachingHelpers
([subject.class.reactive_cache_key.call(subject)].flatten + qualifiers).join(':')
end
- def stub_reactive_cache(subject = nil, data = nil)
+ def alive_reactive_cache_key(subject, *qualifiers)
+ reactive_cache_key(subject, *(qualifiers + ['alive']))
+ end
+
+ def stub_reactive_cache(subject = nil, data = nil, *qualifiers)
allow(ReactiveCachingWorker).to receive(:perform_async)
allow(ReactiveCachingWorker).to receive(:perform_in)
- write_reactive_cache(subject, data) if data
+ write_reactive_cache(subject, data, *qualifiers) if data
end
- def read_reactive_cache(subject)
- Rails.cache.read(reactive_cache_key(subject))
+ def read_reactive_cache(subject, *qualifiers)
+ Rails.cache.read(reactive_cache_key(subject, *qualifiers))
end
- def write_reactive_cache(subject, data)
- start_reactive_cache_lifetime(subject)
- Rails.cache.write(reactive_cache_key(subject), data)
+ def write_reactive_cache(subject, data, *qualifiers)
+ start_reactive_cache_lifetime(subject, *qualifiers)
+ Rails.cache.write(reactive_cache_key(subject, *qualifiers), data)
end
- def reactive_cache_alive?(subject)
- Rails.cache.read(reactive_cache_key(subject, 'alive'))
+ def reactive_cache_alive?(subject, *qualifiers)
+ Rails.cache.read(alive_reactive_cache_key(subject, *qualifiers))
end
- def invalidate_reactive_cache(subject)
- Rails.cache.delete(reactive_cache_key(subject, 'alive'))
+ def invalidate_reactive_cache(subject, *qualifiers)
+ Rails.cache.delete(alive_reactive_cache_key(subject, *qualifiers))
end
- def start_reactive_cache_lifetime(subject)
- Rails.cache.write(reactive_cache_key(subject, 'alive'), true)
+ def start_reactive_cache_lifetime(subject, *qualifiers)
+ Rails.cache.write(alive_reactive_cache_key(subject, *qualifiers), true)
end
def expect_reactive_cache_update_queued(subject)
diff --git a/spec/support/seed_helper.rb b/spec/support/seed_helper.rb
index 3f8398a31e3..03fa0a66b9a 100644
--- a/spec/support/seed_helper.rb
+++ b/spec/support/seed_helper.rb
@@ -25,32 +25,32 @@ module SeedHelper
end
def create_bare_seeds
- system(git_env, *%W(git clone --bare #{GITLAB_URL}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{GITLAB_URL}),
chdir: SEED_REPOSITORY_PATH,
out: '/dev/null',
err: '/dev/null')
end
def create_normal_seeds
- system(git_env, *%W(git clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_NORMAL_REPO_PATH}),
out: '/dev/null',
err: '/dev/null')
end
def create_mutable_seeds
- system(git_env, *%W(git clone #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone #{TEST_REPO_PATH} #{TEST_MUTABLE_REPO_PATH}),
out: '/dev/null',
err: '/dev/null')
system(git_env, *%w(git branch -t feature origin/feature),
chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
- system(git_env, *%W(git remote add expendable #{GITLAB_URL}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} remote add expendable #{GITLAB_URL}),
chdir: TEST_MUTABLE_REPO_PATH, out: '/dev/null', err: '/dev/null')
end
def create_broken_seeds
- system(git_env, *%W(git clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}),
+ system(git_env, *%W(#{Gitlab.config.git.bin_path} clone --bare #{TEST_REPO_PATH} #{TEST_BROKEN_REPO_PATH}),
out: '/dev/null',
err: '/dev/null')
diff --git a/spec/support/stub_env.rb b/spec/support/stub_env.rb
new file mode 100644
index 00000000000..18597b5c71f
--- /dev/null
+++ b/spec/support/stub_env.rb
@@ -0,0 +1,7 @@
+module StubENV
+ def stub_env(key, value)
+ allow(ENV).to receive(:[]).and_call_original unless @env_already_stubbed
+ @env_already_stubbed ||= true
+ allow(ENV).to receive(:[]).with(key).and_return(value)
+ end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index a9fea5f1e81..bc751d20ce1 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -41,7 +41,7 @@ describe 'gitlab:app namespace rake task' do
context 'gitlab version' do
before do
- allow(Dir).to receive(:glob).and_return([])
+ allow(Dir).to receive(:glob).and_return(['1_gitlab_backup.tar'])
allow(Dir).to receive(:chdir)
allow(File).to receive(:exist?).and_return(true)
allow(Kernel).to receive(:system).and_return(true)
diff --git a/spec/views/projects/merge_requests/show.html.haml_spec.rb b/spec/views/projects/merge_requests/show.html.haml_spec.rb
index 33cabd14913..7f123b15194 100644
--- a/spec/views/projects/merge_requests/show.html.haml_spec.rb
+++ b/spec/views/projects/merge_requests/show.html.haml_spec.rb
@@ -7,6 +7,7 @@ describe 'projects/merge_requests/show.html.haml' do
let(:project) { create(:project) }
let(:fork_project) { create(:project, forked_from_project: project) }
let(:unlink_project) { Projects::UnlinkForkService.new(fork_project, user) }
+ let(:note) { create(:note_on_merge_request, project: project, noteable: closed_merge_request) }
let(:closed_merge_request) do
create(:closed_merge_request,
@@ -19,8 +20,12 @@ describe 'projects/merge_requests/show.html.haml' do
assign(:project, project)
assign(:merge_request, closed_merge_request)
assign(:commits_count, 0)
+ assign(:note, note)
+ assign(:noteable, closed_merge_request)
+ assign(:notes, [])
+ assign(:pipelines, Ci::Pipeline.none)
- allow(view).to receive(:can?).and_return(true)
+ allow(view).to receive_messages(current_user: user, can?: true)
end
context 'when the merge request is closed' do
diff --git a/spec/views/shared/milestones/_issuables.html.haml.rb b/spec/views/shared/milestones/_issuables.html.haml.rb
new file mode 100644
index 00000000000..4769d569548
--- /dev/null
+++ b/spec/views/shared/milestones/_issuables.html.haml.rb
@@ -0,0 +1,32 @@
+require 'spec_helper'
+
+describe 'shared/milestones/_issuables.html.haml' do
+ let(:issuables_size) { 100 }
+
+ before do
+ allow(view).to receive_messages(title: nil, id: nil, show_project_name: nil,
+ show_full_project_name: nil, dom_class: '',
+ issuables: double(size: issuables_size).as_null_object)
+
+ stub_template 'shared/milestones/_issuable.html.haml' => ''
+ end
+
+ it 'should show the issuables count if show_counter is true' do
+ render 'shared/milestones/issuables', show_counter: true
+ expect(rendered).to have_content('100')
+ end
+
+ it 'should not show the issuables count if show_counter is false' do
+ render 'shared/milestones/issuables', show_counter: false
+ expect(rendered).not_to have_content('100')
+ end
+
+ describe 'a high issuables count' do
+ let(:issuables_size) { 1000 }
+
+ it 'should show a delimited number if show_counter is true' do
+ render 'shared/milestones/issuables', show_counter: true
+ expect(rendered).to have_content('1,000')
+ end
+ end
+end
diff --git a/spec/workers/use_key_worker_spec.rb b/spec/workers/use_key_worker_spec.rb
new file mode 100644
index 00000000000..e50c788b82a
--- /dev/null
+++ b/spec/workers/use_key_worker_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe UseKeyWorker do
+ describe "#perform" do
+ it "updates the key's last_used_at attribute to the current time when it exists" do
+ worker = described_class.new
+ key = create(:key)
+ current_time = Time.zone.now
+
+ Timecop.freeze(current_time) do
+ expect { worker.perform(key.id) }
+ .to change { key.reload.last_used_at }.from(nil).to be_like_time(current_time)
+ end
+ end
+
+ it "returns false and skips the job when the key doesn't exist" do
+ worker = described_class.new
+ key = create(:key)
+
+ expect(worker.perform(key.id + 1)).to eq false
+ end
+ end
+end