summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorFelipe Artur <fcardozo@gitlab.com>2018-03-06 16:28:54 +0000
committerFelipe Artur <fcardozo@gitlab.com>2018-03-06 16:28:54 +0000
commite77c4e9efe0e19187929e5836cda5a3a59d0f89f (patch)
tree91daaa89bb48457456f931c6b818f5e200390b56 /spec
parent1e137c273ca6314d0ed6744910b95f179b1d538c (diff)
parent9a8f5a2b605f85ace3c81a32cf1855f79cabde43 (diff)
downloadgitlab-ce-e77c4e9efe0e19187929e5836cda5a3a59d0f89f.tar.gz
Merge branch 'master' into 'issue_38337'
# Conflicts: # app/models/group.rb # db/schema.rb
Diffstat (limited to 'spec')
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb4
-rw-r--r--spec/controllers/projects/branches_controller_spec.rb33
-rw-r--r--spec/factories/badge.rb14
-rw-r--r--spec/factories/lfs_objects.rb6
-rw-r--r--spec/features/admin/admin_groups_spec.rb2
-rw-r--r--spec/features/admin/admin_projects_spec.rb2
-rw-r--r--spec/features/admin/admin_runners_spec.rb8
-rw-r--r--spec/features/dashboard/issues_spec.rb8
-rw-r--r--spec/features/dashboard/merge_requests_spec.rb4
-rw-r--r--spec/features/dashboard/projects_spec.rb8
-rw-r--r--spec/features/dashboard/todos/todos_filtering_spec.rb8
-rw-r--r--spec/features/issues/move_spec.rb2
-rw-r--r--spec/features/issues/user_uses_slash_commands_spec.rb3
-rw-r--r--spec/features/projects/branches/download_buttons_spec.rb2
-rw-r--r--spec/features/projects/branches_spec.rb137
-rw-r--r--spec/features/projects/environments/environment_spec.rb2
-rw-r--r--spec/features/projects/import_export/test_project_export.tar.gzbin343092 -> 343087 bytes
-rw-r--r--spec/features/projects/members/master_manages_access_requests_spec.rb4
-rw-r--r--spec/features/projects/members/user_requests_access_spec.rb2
-rw-r--r--spec/features/projects/merge_request_button_spec.rb4
-rw-r--r--spec/features/projects/new_project_spec.rb2
-rw-r--r--spec/features/projects/pages_spec.rb2
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb39
-rw-r--r--spec/features/projects/settings/user_manages_project_members_spec.rb2
-rw-r--r--spec/features/projects/tree/create_directory_spec.rb57
-rw-r--r--spec/features/projects/tree/create_file_spec.rb47
-rw-r--r--spec/features/projects/tree/upload_file_spec.rb53
-rw-r--r--spec/features/search/user_searches_for_code_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_issues_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_merge_requests_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_milestones_spec.rb2
-rw-r--r--spec/features/search/user_searches_for_wiki_pages_spec.rb2
-rw-r--r--spec/features/search/user_uses_search_filters_spec.rb6
-rw-r--r--spec/finders/issues_finder_spec.rb42
-rw-r--r--spec/finders/merge_requests_finder_spec.rb51
-rw-r--r--spec/finders/notes_finder_spec.rb12
-rw-r--r--spec/finders/todos_finder_spec.rb27
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project/export_status.json17
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/project/identity.json21
-rw-r--r--spec/fixtures/emails/update_commands_only_reply.eml38
-rw-r--r--spec/helpers/members_helper_spec.rb10
-rw-r--r--spec/helpers/todos_helper_spec.rb4
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/boards/issue_card_spec.js2
-rw-r--r--spec/javascripts/boards/issue_spec.js2
-rw-r--r--spec/javascripts/boards/list_spec.js2
-rw-r--r--spec/javascripts/boards/modal_store_spec.js2
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js18
-rw-r--r--spec/javascripts/groups/components/app_spec.js59
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js15
-rw-r--r--spec/javascripts/pipelines/blank_state_spec.js29
-rw-r--r--spec/javascripts/pipelines/empty_state_spec.js28
-rw-r--r--spec/javascripts/pipelines/error_state_spec.js27
-rw-r--r--spec/javascripts/pipelines/nav_controls_spec.js84
-rw-r--r--spec/javascripts/pipelines/pipelines_spec.js677
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js33
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_item_spec.js53
-rw-r--r--spec/javascripts/repo/components/commit_sidebar/list_spec.js59
-rw-r--r--spec/javascripts/repo/components/ide_context_bar_spec.js49
-rw-r--r--spec/javascripts/repo/components/ide_repo_tree_spec.js63
-rw-r--r--spec/javascripts/repo/components/ide_side_bar_spec.js43
-rw-r--r--spec/javascripts/repo/components/ide_spec.js39
-rw-r--r--spec/javascripts/repo/components/new_branch_form_spec.js114
-rw-r--r--spec/javascripts/repo/components/new_dropdown/index_spec.js77
-rw-r--r--spec/javascripts/repo/components/new_dropdown/modal_spec.js237
-rw-r--r--spec/javascripts/repo/components/new_dropdown/upload_spec.js158
-rw-r--r--spec/javascripts/repo/components/repo_commit_section_spec.js140
-rw-r--r--spec/javascripts/repo/components/repo_edit_button_spec.js83
-rw-r--r--spec/javascripts/repo/components/repo_editor_spec.js60
-rw-r--r--spec/javascripts/repo/components/repo_file_buttons_spec.js49
-rw-r--r--spec/javascripts/repo/components/repo_file_spec.js98
-rw-r--r--spec/javascripts/repo/components/repo_loading_file_spec.js63
-rw-r--r--spec/javascripts/repo/components/repo_prev_directory_spec.js45
-rw-r--r--spec/javascripts/repo/components/repo_preview_spec.js37
-rw-r--r--spec/javascripts/repo/components/repo_tab_spec.js108
-rw-r--r--spec/javascripts/repo/components/repo_tabs_spec.js37
-rw-r--r--spec/javascripts/repo/helpers.js16
-rw-r--r--spec/javascripts/repo/lib/common/disposable_spec.js44
-rw-r--r--spec/javascripts/repo/lib/common/model_manager_spec.js81
-rw-r--r--spec/javascripts/repo/lib/common/model_spec.js84
-rw-r--r--spec/javascripts/repo/lib/decorations/controller_spec.js120
-rw-r--r--spec/javascripts/repo/lib/diff/controller_spec.js176
-rw-r--r--spec/javascripts/repo/lib/diff/diff_spec.js80
-rw-r--r--spec/javascripts/repo/lib/editor_options_spec.js7
-rw-r--r--spec/javascripts/repo/lib/editor_spec.js128
-rw-r--r--spec/javascripts/repo/monaco_loader_spec.js13
-rw-r--r--spec/javascripts/repo/stores/actions/branch_spec.js44
-rw-r--r--spec/javascripts/repo/stores/actions/file_spec.js431
-rw-r--r--spec/javascripts/repo/stores/actions/tree_spec.js350
-rw-r--r--spec/javascripts/repo/stores/actions_spec.js432
-rw-r--r--spec/javascripts/repo/stores/getters_spec.js114
-rw-r--r--spec/javascripts/repo/stores/mutations/branch_spec.js18
-rw-r--r--spec/javascripts/repo/stores/mutations/file_spec.js131
-rw-r--r--spec/javascripts/repo/stores/mutations/tree_spec.js71
-rw-r--r--spec/javascripts/repo/stores/mutations_spec.js125
-rw-r--r--spec/javascripts/repo/stores/utils_spec.js119
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js81
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js82
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js84
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js42
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js36
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js37
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js39
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js42
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js74
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js94
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js49
-rw-r--r--spec/lib/backup/repository_spec.rb21
-rw-r--r--spec/lib/banzai/filter/autolink_filter_spec.rb126
-rw-r--r--spec/lib/banzai/filter/label_reference_filter_spec.rb8
-rw-r--r--spec/lib/gitlab/checks/lfs_integrity_spec.rb22
-rw-r--r--spec/lib/gitlab/ci/trace_spec.rb134
-rw-r--r--spec/lib/gitlab/contributions_calendar_spec.rb14
-rw-r--r--spec/lib/gitlab/data_builder/build_spec.rb2
-rw-r--r--spec/lib/gitlab/email/handler/create_note_handler_spec.rb27
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb29
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb64
-rw-r--r--spec/lib/gitlab/git/lfs_changes_spec.rb38
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb360
-rw-r--r--spec/lib/gitlab/gitaly_client/blob_service_spec.rb60
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml3
-rw-r--r--spec/lib/gitlab/import_export/avatar_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/avatar_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/file_importer_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/fork_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/project.json767
-rw-r--r--spec/lib/gitlab/import_export/project_tree_restorer_spec.rb10
-rw-r--r--spec/lib/gitlab/import_export/project_tree_saver_spec.rb9
-rw-r--r--spec/lib/gitlab/import_export/reader_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml9
-rw-r--r--spec/lib/gitlab/import_export/uploads_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/uploads_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/version_checker_spec.rb3
-rw-r--r--spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb2
-rw-r--r--spec/lib/gitlab/import_export/wiki_restorer_spec.rb2
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb25
-rw-r--r--spec/lib/gitlab/middleware/release_env_spec.rb16
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb41
-rw-r--r--spec/lib/gitlab/search_results_spec.rb36
-rw-r--r--spec/lib/gitlab/string_placeholder_replacer_spec.rb38
-rw-r--r--spec/lib/gitlab/string_regex_marker_spec.rb35
-rw-r--r--spec/lib/gitlab/verify/lfs_objects_spec.rb35
-rw-r--r--spec/lib/gitlab/verify/uploads_spec.rb44
-rw-r--r--spec/mailers/notify_spec.rb22
-rw-r--r--spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb66
-rw-r--r--spec/models/badge_spec.rb94
-rw-r--r--spec/models/badges/group_badge_spec.rb11
-rw-r--r--spec/models/badges/project_badge_spec.rb43
-rw-r--r--spec/models/clusters/applications/runner_spec.rb30
-rw-r--r--spec/models/group_spec.rb1
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb4
-rw-r--r--spec/models/project_services/jira_service_spec.rb3
-rw-r--r--spec/models/project_services/mattermost_slash_commands_service_spec.rb6
-rw-r--r--spec/models/project_spec.rb47
-rw-r--r--spec/models/project_wiki_spec.rb6
-rw-r--r--spec/models/user_spec.rb26
-rw-r--r--spec/requests/api/badges_spec.rb367
-rw-r--r--spec/requests/api/branches_spec.rb21
-rw-r--r--spec/requests/api/issues_spec.rb36
-rw-r--r--spec/requests/api/merge_requests_spec.rb36
-rw-r--r--spec/requests/api/pages_domains_spec.rb2
-rw-r--r--spec/requests/api/project_export_spec.rb290
-rw-r--r--spec/requests/api/runner_spec.rb8
-rw-r--r--spec/requests/git_http_spec.rb6
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb18
-rw-r--r--spec/services/ci/create_trace_artifact_service_spec.rb63
-rw-r--r--spec/services/merge_requests/create_service_spec.rb35
-rw-r--r--spec/services/notes/create_service_spec.rb51
-rw-r--r--spec/services/notes/quick_actions_service_spec.rb30
-rw-r--r--spec/services/projects/update_pages_service_spec.rb16
-rw-r--r--spec/services/system_hooks_service_spec.rb12
-rw-r--r--spec/services/system_note_service_spec.rb6
-rw-r--r--spec/support/bare_repo_operations.rb14
-rw-r--r--spec/support/features/issuable_slash_commands_shared_examples.rb5
-rw-r--r--spec/support/gitlab_verify.rb45
-rw-r--r--spec/tasks/gitlab/lfs/check_rake_spec.rb28
-rw-r--r--spec/tasks/gitlab/traces_rake_spec.rb55
-rw-r--r--spec/tasks/gitlab/uploads/check_rake_spec.rb (renamed from spec/tasks/gitlab/uploads_rake_spec.rb)13
-rw-r--r--spec/validators/url_placeholder_validator_spec.rb39
-rw-r--r--spec/validators/url_validator_spec.rb46
-rw-r--r--spec/views/projects/_home_panel.html.haml_spec.rb54
-rw-r--r--spec/workers/archive_trace_worker_spec.rb (renamed from spec/workers/create_trace_artifact_worker_spec.rb)8
-rw-r--r--spec/workers/build_finished_worker_spec.rb2
-rw-r--r--spec/workers/concerns/gitlab/github_import/object_importer_spec.rb2
-rw-r--r--spec/workers/concerns/pipeline_background_queue_spec.rb19
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb6
-rw-r--r--spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_issue_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_note_worker_spec.rb2
-rw-r--r--spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb2
-rw-r--r--spec/workers/process_commit_worker_spec.rb74
195 files changed, 4531 insertions, 5775 deletions
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index b7257fac608..fb6d82d7de3 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -246,7 +246,7 @@ describe AutocompleteController do
expect(json_response.size).to eq(1)
expect(json_response.first['id']).to eq authorized_project.id
- expect(json_response.first['name_with_namespace']).to eq authorized_project.name_with_namespace
+ expect(json_response.first['name_with_namespace']).to eq authorized_project.full_name
end
end
end
@@ -267,7 +267,7 @@ describe AutocompleteController do
expect(json_response.size).to eq(1)
expect(json_response.first['id']).to eq authorized_search_project.id
- expect(json_response.first['name_with_namespace']).to eq authorized_search_project.name_with_namespace
+ expect(json_response.first['name_with_namespace']).to eq authorized_search_project.full_name
end
end
end
diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb
index 734396ddf7b..3b9e06cb5ad 100644
--- a/spec/controllers/projects/branches_controller_spec.rb
+++ b/spec/controllers/projects/branches_controller_spec.rb
@@ -407,10 +407,43 @@ describe Projects::BranchesController do
get :index,
namespace_id: project.namespace,
project_id: project,
+ state: 'all',
format: :html
expect(response).to have_gitlab_http_status(200)
end
end
+
+ context 'when depreated sort/search/page parameters are specified' do
+ it 'returns with a status 301 when sort specified' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ sort: 'updated_asc',
+ format: :html
+
+ expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+ end
+
+ it 'returns with a status 301 when search specified' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ search: 'feature',
+ format: :html
+
+ expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+ end
+
+ it 'returns with a status 301 when page specified' do
+ get :index,
+ namespace_id: project.namespace,
+ project_id: project,
+ page: 2,
+ format: :html
+
+ expect(response).to redirect_to project_branches_filtered_path(project, state: 'all')
+ end
+ end
end
end
diff --git a/spec/factories/badge.rb b/spec/factories/badge.rb
new file mode 100644
index 00000000000..b87ece946cb
--- /dev/null
+++ b/spec/factories/badge.rb
@@ -0,0 +1,14 @@
+FactoryBot.define do
+ trait :base_badge do
+ link_url { generate(:url) }
+ image_url { generate(:url) }
+ end
+
+ factory :project_badge, traits: [:base_badge], class: ProjectBadge do
+ project
+ end
+
+ factory :group_badge, aliases: [:badge], traits: [:base_badge], class: GroupBadge do
+ group
+ end
+end
diff --git a/spec/factories/lfs_objects.rb b/spec/factories/lfs_objects.rb
index 8eb709022ce..caaed4d5246 100644
--- a/spec/factories/lfs_objects.rb
+++ b/spec/factories/lfs_objects.rb
@@ -9,4 +9,10 @@ FactoryBot.define do
trait :with_file do
file { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") }
end
+
+ # The uniqueness constraint means we can't use the correct OID for all LFS
+ # objects, so the test needs to decide which (if any) object gets it
+ trait :correct_oid do
+ oid 'b804383982bb89b00e828e3f44c038cc991d3d1768009fc39ba8e2c081b9fb75'
+ end
end
diff --git a/spec/features/admin/admin_groups_spec.rb b/spec/features/admin/admin_groups_spec.rb
index a5f22848031..d5e603baeae 100644
--- a/spec/features/admin/admin_groups_spec.rb
+++ b/spec/features/admin/admin_groups_spec.rb
@@ -173,7 +173,7 @@ feature 'Admin Groups' do
visit admin_group_path(group)
- expect(page).to have_content(empty_project.name_with_namespace)
+ expect(page).to have_content(empty_project.full_name)
expect(page).to have_content('Projects shared with')
end
end
diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb
index d02ac6c2e2a..6d8350e99f1 100644
--- a/spec/features/admin/admin_projects_spec.rb
+++ b/spec/features/admin/admin_projects_spec.rb
@@ -58,7 +58,7 @@ describe "Admin::Projects" do
expect(current_path).to eq admin_project_path(project)
expect(page).to have_content(project.path)
expect(page).to have_content(project.name)
- expect(page).to have_content(project.name_with_namespace)
+ expect(page).to have_content(project.full_name)
expect(page).to have_content(project.creator.name)
end
end
diff --git a/spec/features/admin/admin_runners_spec.rb b/spec/features/admin/admin_runners_spec.rb
index 7eeed7da998..8de2e3d199b 100644
--- a/spec/features/admin/admin_runners_spec.rb
+++ b/spec/features/admin/admin_runners_spec.rb
@@ -76,8 +76,8 @@ describe "Admin Runners" do
describe 'projects' do
it 'contains project names' do
- expect(page).to have_content(@project1.name_with_namespace)
- expect(page).to have_content(@project2.name_with_namespace)
+ expect(page).to have_content(@project1.full_name)
+ expect(page).to have_content(@project2.full_name)
end
end
@@ -89,8 +89,8 @@ describe "Admin Runners" do
end
it 'contains name of correct project' do
- expect(page).to have_content(@project1.name_with_namespace)
- expect(page).not_to have_content(@project2.name_with_namespace)
+ expect(page).to have_content(@project1.full_name)
+ expect(page).not_to have_content(@project2.full_name)
end
end
diff --git a/spec/features/dashboard/issues_spec.rb b/spec/features/dashboard/issues_spec.rb
index 54652e2d849..8d1d5a51750 100644
--- a/spec/features/dashboard/issues_spec.rb
+++ b/spec/features/dashboard/issues_spec.rb
@@ -74,8 +74,8 @@ RSpec.describe 'Dashboard Issues' do
find('.new-project-item-select-button').click
page.within('.select2-results') do
- expect(page).to have_content(project.name_with_namespace)
- expect(page).not_to have_content(project_with_issues_disabled.name_with_namespace)
+ expect(page).to have_content(project.full_name)
+ expect(page).not_to have_content(project_with_issues_disabled.full_name)
end
end
@@ -84,8 +84,8 @@ RSpec.describe 'Dashboard Issues' do
wait_for_requests
- project_path = "/#{project.path_with_namespace}"
- project_json = { name: project.name_with_namespace, url: project_path }.to_json
+ project_path = "/#{project.full_path}"
+ project_json = { name: project.full_name, url: project_path }.to_json
# simulate selection, and prevent overlap by dropdown menu
first('.project-item-select', visible: false)
diff --git a/spec/features/dashboard/merge_requests_spec.rb b/spec/features/dashboard/merge_requests_spec.rb
index 744041ac425..c8f3a8449f5 100644
--- a/spec/features/dashboard/merge_requests_spec.rb
+++ b/spec/features/dashboard/merge_requests_spec.rb
@@ -28,8 +28,8 @@ feature 'Dashboard Merge Requests' do
find('.new-project-item-select-button').click
page.within('.select2-results') do
- expect(page).to have_content(project.name_with_namespace)
- expect(page).not_to have_content(project_with_disabled_merge_requests.name_with_namespace)
+ expect(page).to have_content(project.full_name)
+ expect(page).not_to have_content(project_with_disabled_merge_requests.full_name)
end
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 586c7b48d0b..986f864f0b5 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -37,6 +37,14 @@ feature 'Dashboard Projects' do
expect(page).to have_xpath("//time[@datetime='#{project.last_repository_updated_at.getutc.iso8601}']")
end
+
+ it 'shows the last_activity_at attribute as the update date' do
+ project.update_attributes!(last_repository_updated_at: 1.hour.ago, last_activity_at: Time.now)
+
+ visit dashboard_projects_path
+
+ expect(page).to have_xpath("//time[@datetime='#{project.last_activity_at.getutc.iso8601}']")
+ end
end
context 'when last_repository_updated_at and last_activity_at are missing' do
diff --git a/spec/features/dashboard/todos/todos_filtering_spec.rb b/spec/features/dashboard/todos/todos_filtering_spec.rb
index 2fc34301d51..7b359b0c651 100644
--- a/spec/features/dashboard/todos/todos_filtering_spec.rb
+++ b/spec/features/dashboard/todos/todos_filtering_spec.rb
@@ -24,14 +24,14 @@ feature 'Dashboard > User filters todos', :js do
it 'filters by project' do
click_button 'Project'
within '.dropdown-menu-project' do
- fill_in 'Search projects', with: project_1.name_with_namespace
- click_link project_1.name_with_namespace
+ fill_in 'Search projects', with: project_1.full_name
+ click_link project_1.full_name
end
wait_for_requests
- expect(page).to have_content project_1.name_with_namespace
- expect(page).not_to have_content project_2.name_with_namespace
+ expect(page).to have_content project_1.full_name
+ expect(page).not_to have_content project_2.full_name
end
context 'Author filter' do
diff --git a/spec/features/issues/move_spec.rb b/spec/features/issues/move_spec.rb
index 076a02150a4..3c01ff345fc 100644
--- a/spec/features/issues/move_spec.rb
+++ b/spec/features/issues/move_spec.rb
@@ -73,7 +73,7 @@ feature 'issue move to another project' do
wait_for_requests
page.within '.js-sidebar-move-issue-block' do
- expect(page).to have_content new_project.name_with_namespace
+ expect(page).to have_content new_project.full_name
end
end
end
diff --git a/spec/features/issues/user_uses_slash_commands_spec.rb b/spec/features/issues/user_uses_slash_commands_spec.rb
index e711a191db2..ea7a97d02a0 100644
--- a/spec/features/issues/user_uses_slash_commands_spec.rb
+++ b/spec/features/issues/user_uses_slash_commands_spec.rb
@@ -59,7 +59,6 @@ feature 'Issues > User uses quick actions', :js do
it 'does not create a note, and sets the due date accordingly' do
write_note("/due 2016-08-28")
- expect(page).to have_content '/due 2016-08-28'
expect(page).not_to have_content 'Commands applied'
issue.reload
@@ -99,7 +98,6 @@ feature 'Issues > User uses quick actions', :js do
it 'does not create a note, and sets the due date accordingly' do
write_note("/remove_due_date")
- expect(page).to have_content '/remove_due_date'
expect(page).not_to have_content 'Commands applied'
issue.reload
@@ -147,7 +145,6 @@ feature 'Issues > User uses quick actions', :js do
it 'does not create a note, and does not mark the issue as a duplicate' do
write_note("/duplicate ##{original_issue.to_reference}")
- expect(page).to have_content "/duplicate ##{original_issue.to_reference}"
expect(page).not_to have_content 'Commands applied'
expect(page).not_to have_content "marked this issue as a duplicate of #{original_issue.to_reference}"
diff --git a/spec/features/projects/branches/download_buttons_spec.rb b/spec/features/projects/branches/download_buttons_spec.rb
index 39bcea013e7..605298ba8ab 100644
--- a/spec/features/projects/branches/download_buttons_spec.rb
+++ b/spec/features/projects/branches/download_buttons_spec.rb
@@ -29,7 +29,7 @@ feature 'Download buttons in branches page' do
describe 'when checking branches' do
context 'with artifacts' do
before do
- visit project_branches_path(project, search: 'binary-encoding')
+ visit project_branches_filtered_path(project, state: 'all', search: 'binary-encoding')
end
scenario 'shows download artifacts button' do
diff --git a/spec/features/projects/branches_spec.rb b/spec/features/projects/branches_spec.rb
index 2fddd274078..2a9d9e6416c 100644
--- a/spec/features/projects/branches_spec.rb
+++ b/spec/features/projects/branches_spec.rb
@@ -11,15 +11,109 @@ describe 'Branches' do
project.add_developer(user)
end
- describe 'Initial branches page' do
- it 'shows all the branches sorted by last updated by default' do
+ context 'on the projects with 6 active branches and 4 stale branches' do
+ let(:project) { create(:project, :public, :empty_repo) }
+ let(:repository) { project.repository }
+ let(:threshold) { Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD }
+
+ before do
+ # Add 4 stale branches
+ (1..4).reverse_each do |i|
+ Timecop.freeze((threshold + i).ago) { create_file(message: "a commit in stale-#{i}", branch_name: "stale-#{i}") }
+ end
+ # Add 6 active branches
+ (1..6).each do |i|
+ Timecop.freeze((threshold - i).ago) { create_file(message: "a commit in active-#{i}", branch_name: "active-#{i}") }
+ end
+ end
+
+ describe 'Overview page of the branches' do
+ it 'shows the first 5 active branches and the first 4 stale branches sorted by last updated' do
+ visit project_branches_path(project)
+
+ expect(page).to have_content(sorted_branches(repository, count: 5, sort_by: :updated_desc, state: 'active'))
+ expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
+
+ expect(page).to have_link('Show more active branches', href: project_branches_filtered_path(project, state: 'active'))
+ expect(page).not_to have_content('Show more stale branches')
+ end
+ end
+
+ describe 'Active branches page' do
+ it 'shows 6 active branches sorted by last updated' do
+ visit project_branches_filtered_path(project, state: 'active')
+
+ expect(page).to have_content(sorted_branches(repository, count: 6, sort_by: :updated_desc, state: 'active'))
+ end
+ end
+
+ describe 'Stale branches page' do
+ it 'shows 4 active branches sorted by last updated' do
+ visit project_branches_filtered_path(project, state: 'stale')
+
+ expect(page).to have_content(sorted_branches(repository, count: 4, sort_by: :updated_desc, state: 'stale'))
+ end
+ end
+
+ describe 'All branches page' do
+ it 'shows 10 branches sorted by last updated' do
+ visit project_branches_filtered_path(project, state: 'all')
+
+ expect(page).to have_content(sorted_branches(repository, count: 10, sort_by: :updated_desc))
+ end
+ end
+
+ context 'with branches over more than one page' do
+ before do
+ allow(Kaminari.config).to receive(:default_per_page).and_return(5)
+ end
+
+ it 'shows only default_per_page active branches sorted by last updated' do
+ visit project_branches_filtered_path(project, state: 'active')
+
+ expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc, state: 'active'))
+ end
+
+ it 'shows only default_per_page branches sorted by last updated on All branches' do
+ visit project_branches_filtered_path(project, state: 'all')
+
+ expect(page).to have_content(sorted_branches(repository, count: Kaminari.config.default_per_page, sort_by: :updated_desc))
+ end
+ end
+ end
+
+ describe 'Find branches' do
+ it 'shows filtered branches', :js do
visit project_branches_path(project)
+ fill_in 'branch-search', with: 'fix'
+ find('#branch-search').native.send_keys(:enter)
+
+ expect(page).to have_content('fix')
+ expect(find('.all-branches')).to have_selector('li', count: 1)
+ end
+ end
+
+ describe 'Delete unprotected branch on Overview' do
+ it 'removes branch after confirmation', :js do
+ visit project_branches_filtered_path(project, state: 'all')
+
+ expect(all('.all-branches').last).to have_selector('li', count: 20)
+ accept_confirm { find('.js-branch-add-pdf-text-binary .btn-remove').click }
+
+ expect(all('.all-branches').last).to have_selector('li', count: 19)
+ end
+ end
+
+ describe 'All branches page' do
+ it 'shows all the branches sorted by last updated by default' do
+ visit project_branches_filtered_path(project, state: 'all')
+
expect(page).to have_content(sorted_branches(repository, count: 20, sort_by: :updated_desc))
end
it 'sorts the branches by name' do
- visit project_branches_path(project)
+ visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
click_link "Name"
@@ -28,7 +122,7 @@ describe 'Branches' do
end
it 'sorts the branches by oldest updated' do
- visit project_branches_path(project)
+ visit project_branches_filtered_path(project, state: 'all')
click_button "Last updated" # Open sorting dropdown
click_link "Oldest updated"
@@ -41,13 +135,13 @@ describe 'Branches' do
%w(one two three four five).each { |ref| repository.add_branch(user, ref, 'master') }
- expect { visit project_branches_path(project) }.not_to exceed_query_limit(control_count)
+ expect { visit project_branches_filtered_path(project, state: 'all') }.not_to exceed_query_limit(control_count)
end
end
- describe 'Find branches' do
+ describe 'Find branches on All branches' do
it 'shows filtered branches', :js do
- visit project_branches_path(project)
+ visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix'
find('#branch-search').native.send_keys(:enter)
@@ -57,9 +151,9 @@ describe 'Branches' do
end
end
- describe 'Delete unprotected branch' do
+ describe 'Delete unprotected branch on All branches' do
it 'removes branch after confirmation', :js do
- visit project_branches_path(project)
+ visit project_branches_filtered_path(project, state: 'all')
fill_in 'branch-search', with: 'fix'
@@ -73,6 +167,19 @@ describe 'Branches' do
expect(find('.all-branches')).to have_selector('li', count: 0)
end
end
+
+ context 'on project with 0 branch' do
+ let(:project) { create(:project, :public, :empty_repo) }
+ let(:repository) { project.repository }
+
+ describe '0 branches on Overview' do
+ it 'shows warning' do
+ visit project_branches_path(project)
+
+ expect(page).not_to have_selector('.all-branches')
+ end
+ end
+ end
end
context 'logged in as master' do
@@ -83,7 +190,7 @@ describe 'Branches' do
describe 'Initial branches page' do
it 'shows description for admin' do
- visit project_branches_path(project)
+ visit project_branches_filtered_path(project, state: 'all')
expect(page).to have_content("Protected branches can be managed in project settings")
end
@@ -102,12 +209,18 @@ describe 'Branches' do
end
end
- def sorted_branches(repository, count:, sort_by:)
+ def sorted_branches(repository, count:, sort_by:, state: nil)
+ branches = repository.branches_sorted_by(sort_by)
+ branches = branches.select { |b| state == 'active' ? b.active? : b.stale? } if state
sorted_branches =
- repository.branches_sorted_by(sort_by).first(count).map do |branch|
+ branches.first(count).map do |branch|
Regexp.escape(branch.name)
end
Regexp.new(sorted_branches.join('.*'))
end
+
+ def create_file(message: 'message', branch_name:)
+ repository.create_file(user, generate(:branch), 'content', message: message, branch_name: branch_name)
+ end
end
diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb
index 64e600144e0..b233af83eec 100644
--- a/spec/features/projects/environments/environment_spec.rb
+++ b/spec/features/projects/environments/environment_spec.rb
@@ -234,7 +234,7 @@ feature 'Environment' do
end
scenario 'user deletes the branch with running environment' do
- visit project_branches_path(project, search: 'feature')
+ visit project_branches_filtered_path(project, state: 'all', search: 'feature')
remove_branch_with_hooks(project, user, 'feature') do
page.within('.js-branch-feature') { find('a.btn-remove').click }
diff --git a/spec/features/projects/import_export/test_project_export.tar.gz b/spec/features/projects/import_export/test_project_export.tar.gz
index 0cc68aff494..12bfcc177c7 100644
--- a/spec/features/projects/import_export/test_project_export.tar.gz
+++ b/spec/features/projects/import_export/test_project_export.tar.gz
Binary files differ
diff --git a/spec/features/projects/members/master_manages_access_requests_spec.rb b/spec/features/projects/members/master_manages_access_requests_spec.rb
index d575596937d..1f4eec0a317 100644
--- a/spec/features/projects/members/master_manages_access_requests_spec.rb
+++ b/spec/features/projects/members/master_manages_access_requests_spec.rb
@@ -25,7 +25,7 @@ feature 'Projects > Members > Master manages access requests' do
perform_enqueued_jobs { click_on 'Grant access' }
expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email]
- expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was granted"
+ expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was granted"
end
scenario 'master can deny access' do
@@ -36,7 +36,7 @@ feature 'Projects > Members > Master manages access requests' do
perform_enqueued_jobs { click_on 'Deny access' }
expect(ActionMailer::Base.deliveries.last.to).to eq [user.notification_email]
- expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.name_with_namespace} project was denied"
+ expect(ActionMailer::Base.deliveries.last.subject).to match "Access to the #{project.full_name} project was denied"
end
def expect_visible_access_request(project, user)
diff --git a/spec/features/projects/members/user_requests_access_spec.rb b/spec/features/projects/members/user_requests_access_spec.rb
index 4eb36156812..672d5daa3d8 100644
--- a/spec/features/projects/members/user_requests_access_spec.rb
+++ b/spec/features/projects/members/user_requests_access_spec.rb
@@ -21,7 +21,7 @@ feature 'Projects > Members > User requests access', :js do
perform_enqueued_jobs { click_link 'Request Access' }
expect(ActionMailer::Base.deliveries.last.to).to eq [master.notification_email]
- expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.name_with_namespace} project"
+ expect(ActionMailer::Base.deliveries.last.subject).to eq "Request to join the #{project.full_name} project"
expect(project.requesters.exists?(user_id: user)).to be_truthy
expect(page).to have_content 'Your request for access has been queued for review.'
diff --git a/spec/features/projects/merge_request_button_spec.rb b/spec/features/projects/merge_request_button_spec.rb
index 85d518c0cc3..40689964b91 100644
--- a/spec/features/projects/merge_request_button_spec.rb
+++ b/spec/features/projects/merge_request_button_spec.rb
@@ -81,8 +81,8 @@ feature 'Merge Request button' do
context 'on branches page' do
it_behaves_like 'Merge request button only shown when allowed' do
let(:label) { 'Merge request' }
- let(:url) { project_branches_path(project, search: 'feature') }
- let(:fork_url) { project_branches_path(forked_project, search: 'feature') }
+ let(:url) { project_branches_filtered_path(project, state: 'all', search: 'feature') }
+ let(:fork_url) { project_branches_filtered_path(forked_project, state: 'all', search: 'feature') }
end
end
diff --git a/spec/features/projects/new_project_spec.rb b/spec/features/projects/new_project_spec.rb
index b5104747d00..fd561288091 100644
--- a/spec/features/projects/new_project_spec.rb
+++ b/spec/features/projects/new_project_spec.rb
@@ -142,7 +142,7 @@ feature 'New project' do
context 'from git repository url, "Repo by URL"' do
before do
- first('.import_git').click
+ first('.js-import-git-toggle-button').click
end
it 'does not autocomplete sensitive git repo URL' do
diff --git a/spec/features/projects/pages_spec.rb b/spec/features/projects/pages_spec.rb
index 2a0d235ef04..233d2e67b9d 100644
--- a/spec/features/projects/pages_spec.rb
+++ b/spec/features/projects/pages_spec.rb
@@ -258,7 +258,7 @@ feature 'Pages' do
end
let(:ci_build) do
- build(
+ create(
:ci_build,
project: project,
pipeline: pipeline,
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 3a8e7c05cc4..849d85061df 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -86,7 +86,22 @@ describe 'Pipelines', :js do
it 'updates content when tab is clicked' do
page.find('.js-pipelines-tab-pending').click
wait_for_requests
- expect(page).to have_content('No pipelines to show.')
+ expect(page).to have_content('There are currently no pending pipelines.')
+ end
+ end
+
+ context 'navigation links' do
+ before do
+ visit project_pipelines_path(project)
+ wait_for_requests
+ end
+
+ it 'renders run pipeline link' do
+ expect(page).to have_link('Run Pipeline')
+ end
+
+ it 'renders ci lint link' do
+ expect(page).to have_link('CI Lint')
end
end
@@ -542,7 +557,7 @@ describe 'Pipelines', :js do
end
it 'has a clear caches button' do
- expect(page).to have_link 'Clear runner caches'
+ expect(page).to have_link 'Clear Runner Caches'
end
describe 'user clicks the button' do
@@ -552,19 +567,31 @@ describe 'Pipelines', :js do
end
it 'increments jobs_cache_index' do
- click_link 'Clear runner caches'
+ click_link 'Clear Runner Caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
context 'when project does not have jobs_cache_index' do
it 'sets jobs_cache_index to 1' do
- click_link 'Clear runner caches'
+ click_link 'Clear Runner Caches'
expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.'
end
end
end
end
+
+ describe 'Empty State' do
+ let(:project) { create(:project, :repository) }
+
+ before do
+ visit project_pipelines_path(project)
+ end
+
+ it 'renders empty state' do
+ expect(page).to have_content 'Build with confidence'
+ end
+ end
end
context 'when user is not logged in' do
@@ -575,7 +602,9 @@ describe 'Pipelines', :js do
context 'when project is public' do
let(:project) { create(:project, :public, :repository) }
- it { expect(page).to have_content 'Build with confidence' }
+ context 'without pipelines' do
+ it { expect(page).to have_content 'This project is not currently set up to run pipelines.' }
+ end
end
context 'when project is private' do
diff --git a/spec/features/projects/settings/user_manages_project_members_spec.rb b/spec/features/projects/settings/user_manages_project_members_spec.rb
index 2709047b8de..0a4f57bcd21 100644
--- a/spec/features/projects/settings/user_manages_project_members_spec.rb
+++ b/spec/features/projects/settings/user_manages_project_members_spec.rb
@@ -39,7 +39,7 @@ describe 'User manages project members' do
click_link('Import')
end
- select(project2.name_with_namespace, from: 'source_project_id')
+ select(project2.full_name, from: 'source_project_id')
click_button('Import')
project_member = project.project_members.find_by(user_id: user_mike.id)
diff --git a/spec/features/projects/tree/create_directory_spec.rb b/spec/features/projects/tree/create_directory_spec.rb
deleted file mode 100644
index 0c67196f53e..00000000000
--- a/spec/features/projects/tree/create_directory_spec.rb
+++ /dev/null
@@ -1,57 +0,0 @@
-require 'spec_helper'
-
-feature 'Multi-file editor new directory', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- before do
- project.add_master(user)
- sign_in(user)
-
- set_cookie('new_repo', 'true')
-
- visit project_tree_path(project, :master)
-
- wait_for_requests
-
- click_link('Web IDE')
-
- wait_for_requests
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'creates directory in current directory' do
- find('.add-to-tree').click
-
- click_link('New directory')
-
- page.within('.modal') do
- find('.form-control').set('folder name')
-
- click_button('Create directory')
- end
-
- find('.add-to-tree').click
-
- click_link('New file')
-
- page.within('.modal-dialog') do
- find('.form-control').set('file name')
-
- click_button('Create file')
- end
-
- wait_for_requests
-
- find('.multi-file-commit-panel-collapse-btn').click
-
- fill_in('commit-message', with: 'commit message ide')
-
- click_button('Commit')
-
- expect(page).to have_content('folder name')
- end
-end
diff --git a/spec/features/projects/tree/create_file_spec.rb b/spec/features/projects/tree/create_file_spec.rb
deleted file mode 100644
index 85f7318c05d..00000000000
--- a/spec/features/projects/tree/create_file_spec.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-require 'spec_helper'
-
-feature 'Multi-file editor new file', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
-
- before do
- project.add_master(user)
- sign_in(user)
-
- set_cookie('new_repo', 'true')
-
- visit project_tree_path(project, :master)
-
- wait_for_requests
-
- click_link('Web IDE')
-
- wait_for_requests
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'creates file in current directory' do
- find('.add-to-tree').click
-
- click_link('New file')
-
- page.within('.modal') do
- find('.form-control').set('file name')
-
- click_button('Create file')
- end
-
- wait_for_requests
-
- find('.multi-file-commit-panel-collapse-btn').click
-
- fill_in('commit-message', with: 'commit message ide')
-
- click_button('Commit')
-
- expect(page).to have_content('file name')
- end
-end
diff --git a/spec/features/projects/tree/upload_file_spec.rb b/spec/features/projects/tree/upload_file_spec.rb
deleted file mode 100644
index f81e8677e92..00000000000
--- a/spec/features/projects/tree/upload_file_spec.rb
+++ /dev/null
@@ -1,53 +0,0 @@
-require 'spec_helper'
-
-feature 'Multi-file editor upload file', :js do
- let(:user) { create(:user) }
- let(:project) { create(:project, :repository) }
- let(:txt_file) { File.join(Rails.root, 'spec', 'fixtures', 'doc_sample.txt') }
- let(:img_file) { File.join(Rails.root, 'spec', 'fixtures', 'dk.png') }
-
- before do
- project.add_master(user)
- sign_in(user)
-
- set_cookie('new_repo', 'true')
-
- visit project_tree_path(project, :master)
-
- wait_for_requests
-
- click_link('Web IDE')
-
- wait_for_requests
- end
-
- after do
- set_cookie('new_repo', 'false')
- end
-
- it 'uploads text file' do
- find('.add-to-tree').click
-
- # make the field visible so capybara can use it
- execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
- attach_file('file-upload', txt_file)
-
- find('.add-to-tree').click
-
- expect(page).to have_selector('.multi-file-tab', text: 'doc_sample.txt')
- expect(find('.blob-editor-container .lines-content')['innerText']).to have_content(File.open(txt_file, &:readline))
- end
-
- it 'uploads image file' do
- find('.add-to-tree').click
-
- # make the field visible so capybara can use it
- execute_script('document.querySelector("#file-upload").classList.remove("hidden")')
- attach_file('file-upload', img_file)
-
- find('.add-to-tree').click
-
- expect(page).to have_selector('.multi-file-tab', text: 'dk.png')
- expect(page).not_to have_selector('.monaco-editor')
- end
-end
diff --git a/spec/features/search/user_searches_for_code_spec.rb b/spec/features/search/user_searches_for_code_spec.rb
index 77212fb105b..9e089c5a6cb 100644
--- a/spec/features/search/user_searches_for_code_spec.rb
+++ b/spec/features/search/user_searches_for_code_spec.rb
@@ -35,7 +35,7 @@ describe 'User searches for code' do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
fill_in('dashboard_search', with: 'rspec')
diff --git a/spec/features/search/user_searches_for_issues_spec.rb b/spec/features/search/user_searches_for_issues_spec.rb
index ef9553f2a91..d6120ff8517 100644
--- a/spec/features/search/user_searches_for_issues_spec.rb
+++ b/spec/features/search/user_searches_for_issues_spec.rb
@@ -34,7 +34,7 @@ describe 'User searches for issues', :js do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
fill_in('dashboard_search', with: issue1.title)
diff --git a/spec/features/search/user_searches_for_merge_requests_spec.rb b/spec/features/search/user_searches_for_merge_requests_spec.rb
index 3b6739aecbd..68e2f7a857d 100644
--- a/spec/features/search/user_searches_for_merge_requests_spec.rb
+++ b/spec/features/search/user_searches_for_merge_requests_spec.rb
@@ -33,7 +33,7 @@ describe 'User searches for merge requests', :js do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
fill_in('dashboard_search', with: merge_request1.title)
diff --git a/spec/features/search/user_searches_for_milestones_spec.rb b/spec/features/search/user_searches_for_milestones_spec.rb
index 6e197aee498..fc6cd81eb68 100644
--- a/spec/features/search/user_searches_for_milestones_spec.rb
+++ b/spec/features/search/user_searches_for_milestones_spec.rb
@@ -33,7 +33,7 @@ describe 'User searches for milestones', :js do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
fill_in('dashboard_search', with: milestone1.title)
diff --git a/spec/features/search/user_searches_for_wiki_pages_spec.rb b/spec/features/search/user_searches_for_wiki_pages_spec.rb
index 00af625dc86..7934779058f 100644
--- a/spec/features/search/user_searches_for_wiki_pages_spec.rb
+++ b/spec/features/search/user_searches_for_wiki_pages_spec.rb
@@ -18,7 +18,7 @@ describe 'User searches for wiki pages', :js do
find('.js-search-project-dropdown').click
page.within('.project-filter') do
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
fill_in('dashboard_search', with: 'content')
diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb
index aa883c964d2..66afe163447 100644
--- a/spec/features/search/user_uses_search_filters_spec.rb
+++ b/spec/features/search/user_uses_search_filters_spec.rb
@@ -31,7 +31,7 @@ describe 'User uses search filters', :js do
wait_for_requests
- expect(page).to have_link(group_project.name_with_namespace)
+ expect(page).to have_link(group_project.full_name)
end
end
end
@@ -43,10 +43,10 @@ describe 'User uses search filters', :js do
wait_for_requests
- click_link(project.name_with_namespace)
+ click_link(project.full_name)
end
- expect(find('.js-search-project-dropdown')).to have_content(project.name_with_namespace)
+ expect(find('.js-search-project-dropdown')).to have_content(project.full_name)
end
end
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index abb7631d7d7..45439640ea3 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -10,9 +10,9 @@ describe IssuesFinder do
set(:project3) { create(:project, group: subgroup) }
set(:milestone) { create(:milestone, project: project1) }
set(:label) { create(:label, project: project2) }
- set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago) }
- set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab') }
- set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 1.week.from_now) }
+ set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
+ set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+ set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
set(:issue4) { create(:issue, project: project3) }
set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
@@ -275,12 +275,46 @@ describe IssuesFinder do
end
context 'through created_before' do
- let(:params) { { created_before: issue1.created_at + 1.second } }
+ let(:params) { { created_before: issue1.created_at } }
it 'returns issues created on or before the given date' do
expect(issues).to contain_exactly(issue1)
end
end
+
+ context 'through created_after and created_before' do
+ let(:params) { { created_after: issue2.created_at, created_before: issue3.created_at } }
+
+ it 'returns issues created between the given dates' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
+ end
+
+ context 'filtering by updated_at' do
+ context 'through updated_after' do
+ let(:params) { { updated_after: issue3.updated_at } }
+
+ it 'returns issues updated on or after the given date' do
+ expect(issues).to contain_exactly(issue3)
+ end
+ end
+
+ context 'through updated_before' do
+ let(:params) { { updated_before: issue1.updated_at } }
+
+ it 'returns issues updated on or before the given date' do
+ expect(issues).to contain_exactly(issue1)
+ end
+ end
+
+ context 'through updated_after and updated_before' do
+ let(:params) { { updated_after: issue2.updated_at, updated_before: issue3.updated_at } }
+
+ it 'returns issues updated between the given dates' do
+ expect(issues).to contain_exactly(issue2, issue3)
+ end
+ end
end
context 'filtering by reaction name' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 7917a00fc50..c8a43ddf410 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -109,7 +109,7 @@ describe MergeRequestsFinder do
end
end
- context 'with created_after and created_before params' do
+ context 'filtering by created_at/updated_at' do
let(:new_project) { create(:project, forked_from_project: project1) }
let!(:new_merge_request) do
@@ -117,15 +117,18 @@ describe MergeRequestsFinder do
:simple,
author: user,
created_at: 1.week.from_now,
+ updated_at: 1.week.from_now,
source_project: new_project,
- target_project: project1)
+ target_project: new_project)
end
let!(:old_merge_request) do
create(:merge_request,
:simple,
author: user,
+ source_branch: 'feature_1',
created_at: 1.week.ago,
+ updated_at: 1.week.ago,
source_project: new_project,
target_project: new_project)
end
@@ -135,7 +138,7 @@ describe MergeRequestsFinder do
end
it 'filters by created_after' do
- params = { project_id: project1.id, created_after: new_merge_request.created_at }
+ params = { project_id: new_project.id, created_after: new_merge_request.created_at }
merge_requests = described_class.new(user, params).execute
@@ -143,12 +146,52 @@ describe MergeRequestsFinder do
end
it 'filters by created_before' do
- params = { project_id: new_project.id, created_before: old_merge_request.created_at + 1.second }
+ params = { project_id: new_project.id, created_before: old_merge_request.created_at }
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to contain_exactly(old_merge_request)
end
+
+ it 'filters by created_after and created_before' do
+ params = {
+ project_id: new_project.id,
+ created_after: old_merge_request.created_at,
+ created_before: new_merge_request.created_at
+ }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
+
+ it 'filters by updated_after' do
+ params = { project_id: new_project.id, updated_after: new_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(new_merge_request)
+ end
+
+ it 'filters by updated_before' do
+ params = { project_id: new_project.id, updated_before: old_merge_request.updated_at }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request)
+ end
+
+ it 'filters by updated_after and updated_before' do
+ params = {
+ project_id: new_project.id,
+ updated_after: old_merge_request.updated_at,
+ updated_before: new_merge_request.updated_at
+ }
+
+ merge_requests = described_class.new(user, params).execute
+
+ expect(merge_requests).to contain_exactly(old_merge_request, new_merge_request)
+ end
end
end
diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb
index 7b43494eea2..f1ae2c7ab65 100644
--- a/spec/finders/notes_finder_spec.rb
+++ b/spec/finders/notes_finder_spec.rb
@@ -75,6 +75,18 @@ describe NotesFinder do
end
end
+ context 'for target type' do
+ let(:project) { create(:project, :repository) }
+ let!(:note1) { create :note_on_issue, project: project }
+ let!(:note2) { create :note_on_commit, project: project }
+
+ it 'finds only notes for the selected type' do
+ notes = described_class.new(project, user, target_type: 'issue').execute
+
+ expect(notes).to eq([note1])
+ end
+ end
+
context 'for target' do
let(:project) { create(:project, :repository) }
let(:note1) { create :note_on_commit, project: project }
diff --git a/spec/finders/todos_finder_spec.rb b/spec/finders/todos_finder_spec.rb
index 90eb0fe21e4..9747b9402a7 100644
--- a/spec/finders/todos_finder_spec.rb
+++ b/spec/finders/todos_finder_spec.rb
@@ -2,12 +2,13 @@ require 'spec_helper'
describe TodosFinder do
describe '#execute' do
- let(:user) { create(:user) }
- let(:project) { create(:project) }
- let(:finder) { described_class }
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, namespace: group) }
+ let(:finder) { described_class }
before do
- project.add_developer(user)
+ group.add_developer(user)
end
describe '#sort' do
@@ -34,17 +35,20 @@ describe TodosFinder do
end
it "sorts by priority" do
+ project_2 = create(:project)
+
label_1 = create(:label, title: 'label_1', project: project, priority: 1)
label_2 = create(:label, title: 'label_2', project: project, priority: 2)
label_3 = create(:label, title: 'label_3', project: project, priority: 3)
+ label_1_2 = create(:label, title: 'label_1', project: project_2, priority: 1)
issue_1 = create(:issue, title: 'issue_1', project: project)
issue_2 = create(:issue, title: 'issue_2', project: project)
issue_3 = create(:issue, title: 'issue_3', project: project)
issue_4 = create(:issue, title: 'issue_4', project: project)
- merge_request_1 = create(:merge_request, source_project: project)
+ merge_request_1 = create(:merge_request, source_project: project_2)
- merge_request_1.labels << label_1
+ merge_request_1.labels << label_1_2
# Covers the case where Todo has more than one label
issue_3.labels << label_1
@@ -57,15 +61,14 @@ describe TodosFinder do
todo_2 = create(:todo, user: user, project: project, target: issue_2)
todo_3 = create(:todo, user: user, project: project, target: issue_3, created_at: 2.hours.ago)
todo_4 = create(:todo, user: user, project: project, target: issue_1)
- todo_5 = create(:todo, user: user, project: project, target: merge_request_1, created_at: 1.hour.ago)
+ todo_5 = create(:todo, user: user, project: project_2, target: merge_request_1, created_at: 1.hour.ago)
+
+ project_2.add_developer(user)
todos = finder.new(user, { sort: 'priority' }).execute
- expect(todos.first).to eq(todo_3)
- expect(todos.second).to eq(todo_5)
- expect(todos.third).to eq(todo_4)
- expect(todos.fourth).to eq(todo_2)
- expect(todos.fifth).to eq(todo_1)
+ puts todos.to_sql
+ expect(todos).to eq([todo_3, todo_5, todo_4, todo_2, todo_1])
end
end
end
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/export_status.json b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
new file mode 100644
index 00000000000..d24a6f93f4b
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project/export_status.json
@@ -0,0 +1,17 @@
+{
+ "type": "object",
+ "allOf": [
+ { "$ref": "identity.json" },
+ {
+ "required": [
+ "export_status"
+ ],
+ "properties": {
+ "export_status": {
+ "type": "string",
+ "enum": ["none", "started", "finished"]
+ }
+ }
+ }
+ ]
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/project/identity.json b/spec/fixtures/api/schemas/public_api/v4/project/identity.json
new file mode 100644
index 00000000000..e35ab023d44
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/project/identity.json
@@ -0,0 +1,21 @@
+{
+ "type": "object",
+ "required": [
+ "id",
+ "description",
+ "name",
+ "name_with_namespace",
+ "path",
+ "path_with_namespace",
+ "created_at"
+ ],
+ "properties": {
+ "id": { "type": "integer" },
+ "description": { "type": ["string", "null"] },
+ "name": { "type": "string" },
+ "name_with_namespace": { "type": "string" },
+ "path": { "type": "string" },
+ "path_with_namespace": { "type": "string" },
+ "created_at": { "type": "date" }
+ }
+}
diff --git a/spec/fixtures/emails/update_commands_only_reply.eml b/spec/fixtures/emails/update_commands_only_reply.eml
new file mode 100644
index 00000000000..bb0d2b0e03a
--- /dev/null
+++ b/spec/fixtures/emails/update_commands_only_reply.eml
@@ -0,0 +1,38 @@
+Return-Path: <jake@adventuretime.ooo>
+Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400
+Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 17:03:50 -0400
+Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for <reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo>; Thu, 13 Jun 2013 14:03:48 -0700
+Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700
+Date: Thu, 13 Jun 2013 17:03:48 -0400
+From: Jake the Dog <jake@adventuretime.ooo>
+To: reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo
+Message-ID: <CADkmRc+rNGAGGbV2iE5p918UVy4UyJqVcXRO2=otppgzduJSg@mail.gmail.com>
+In-Reply-To: <issue_1@localhost>
+References: <issue_1@localhost> <reply-59d8df8370b7e95c5a49fbf86aeb2c93@localhost>
+Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux'
+Mime-Version: 1.0
+Content-Type: text/plain;
+ charset=ISO-8859-1
+Content-Transfer-Encoding: 7bit
+X-Sieve: CMU Sieve 2.2
+X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu,
+ 13 Jun 2013 14:03:48 -0700 (PDT)
+X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1
+
+/close
+
+On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta
+<reply+59d8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo> wrote:
+>
+>
+>
+> eviltrout posted in 'Adventure Time Sux' on Discourse Meta:
+>
+> ---
+> hey guys everyone knows adventure time sucks!
+>
+> ---
+> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3
+>
+> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences).
+>
diff --git a/spec/helpers/members_helper_spec.rb b/spec/helpers/members_helper_spec.rb
index 45ffbeb27a4..4590904c93d 100644
--- a/spec/helpers/members_helper_spec.rb
+++ b/spec/helpers/members_helper_spec.rb
@@ -12,10 +12,10 @@ describe MembersHelper do
let(:group_member_invite) { build(:group_member, group: group).tap { |m| m.generate_invite_token! } }
let(:group_member_request) { group.request_access(requester) }
- it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.name_with_namespace} project?" }
- it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.name_with_namespace} project?" }
- it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.name_with_namespace} project?" }
- it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.name_with_namespace} project?" }
+ it { expect(remove_member_message(project_member)).to eq "Are you sure you want to remove #{project_member.user.name} from the #{project.full_name} project?" }
+ it { expect(remove_member_message(project_member_invite)).to eq "Are you sure you want to revoke the invitation for #{project_member_invite.invite_email} to join the #{project.full_name} project?" }
+ it { expect(remove_member_message(project_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{project.full_name} project?" }
+ it { expect(remove_member_message(project_member_request, user: requester)).to eq "Are you sure you want to withdraw your access request for the #{project.full_name} project?" }
it { expect(remove_member_message(group_member)).to eq "Are you sure you want to remove #{group_member.user.name} from the #{group.name} group?" }
it { expect(remove_member_message(group_member_invite)).to eq "Are you sure you want to revoke the invitation for #{group_member_invite.invite_email} to join the #{group.name} group?" }
it { expect(remove_member_message(group_member_request)).to eq "Are you sure you want to deny #{requester.name}'s request to join the #{group.name} group?" }
@@ -42,7 +42,7 @@ describe MembersHelper do
let(:group) { build_stubbed(:group) }
let(:user) { build_stubbed(:user) }
- it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.name_with_namespace}\" project?" }
+ it { expect(leave_confirmation_message(project)).to eq "Are you sure you want to leave the \"#{project.full_name}\" project?" }
it { expect(leave_confirmation_message(group)).to eq "Are you sure you want to leave the \"#{group.name}\" group?" }
end
end
diff --git a/spec/helpers/todos_helper_spec.rb b/spec/helpers/todos_helper_spec.rb
index f55163c26e9..63806ef91f3 100644
--- a/spec/helpers/todos_helper_spec.rb
+++ b/spec/helpers/todos_helper_spec.rb
@@ -26,8 +26,8 @@ describe TodosHelper do
expected_results = [
{ 'id' => '', 'text' => 'Any Project' },
- { 'id' => projects.second.id, 'text' => projects.second.name_with_namespace },
- { 'id' => projects.first.id, 'text' => projects.first.name_with_namespace }
+ { 'id' => projects.second.id, 'text' => projects.second.full_name },
+ { 'id' => projects.first.id, 'text' => projects.first.full_name }
]
expect(JSON.parse(helper.todo_projects_options)).to match_array(expected_results)
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 80a598e63bd..13d607a06d2 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -9,8 +9,8 @@ import axios from '~/lib/utils/axios_utils';
import '~/boards/models/assignee';
import eventHub from '~/boards/eventhub';
+import '~/vue_shared/models/label';
import '~/boards/models/list';
-import '~/boards/models/label';
import '~/boards/stores/boards_store';
import boardCard from '~/boards/components/board_card.vue';
import { listObj, boardsMockInterceptor, mockBoardService } from './mock_data';
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 8411f4dd8a6..0cf9e4c9ba1 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -7,8 +7,8 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import Cookies from 'js-cookie';
+import '~/vue_shared/models/label';
import '~/boards/models/issue';
-import '~/boards/models/label';
import '~/boards/models/list';
import '~/boards/models/assignee';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/issue_card_spec.js b/spec/javascripts/boards/issue_card_spec.js
index 278155c585e..37088a6421c 100644
--- a/spec/javascripts/boards/issue_card_spec.js
+++ b/spec/javascripts/boards/issue_card_spec.js
@@ -4,8 +4,8 @@
import Vue from 'vue';
+import '~/vue_shared/models/label';
import '~/boards/models/issue';
-import '~/boards/models/label';
import '~/boards/models/list';
import '~/boards/models/assignee';
import '~/boards/stores/boards_store';
diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js
index dbbe14fe3e0..4a11131b55c 100644
--- a/spec/javascripts/boards/issue_spec.js
+++ b/spec/javascripts/boards/issue_spec.js
@@ -3,8 +3,8 @@
/* global ListIssue */
import Vue from 'vue';
+import '~/vue_shared/models/label';
import '~/boards/models/issue';
-import '~/boards/models/label';
import '~/boards/models/list';
import '~/boards/models/assignee';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index 34964b20b05..d9a1d692949 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -6,8 +6,8 @@
import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import _ from 'underscore';
+import '~/vue_shared/models/label';
import '~/boards/models/issue';
-import '~/boards/models/label';
import '~/boards/models/list';
import '~/boards/models/assignee';
import '~/boards/services/board_service';
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/javascripts/boards/modal_store_spec.js
index 7eecb58a4c3..e9d77f035e3 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/javascripts/boards/modal_store_spec.js
@@ -1,7 +1,7 @@
/* global ListIssue */
+import '~/vue_shared/models/label';
import '~/boards/models/issue';
-import '~/boards/models/label';
import '~/boards/models/list';
import '~/boards/models/assignee';
import '~/boards/stores/modal_store';
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index f1da5f81c0f..756a654765b 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -128,6 +128,24 @@ describe('Filtered Search Visual Tokens', () => {
});
});
+ describe('getEndpointWithQueryParams', () => {
+ it('returns `endpoint` string as is when second param `endpointQueryParams` is undefined, null or empty string', () => {
+ const endpoint = 'foo/bar/labels.json';
+ expect(subject.getEndpointWithQueryParams(endpoint)).toBe(endpoint);
+ expect(subject.getEndpointWithQueryParams(endpoint, null)).toBe(endpoint);
+ expect(subject.getEndpointWithQueryParams(endpoint, '')).toBe(endpoint);
+ });
+
+ it('returns `endpoint` string with values of `endpointQueryParams`', () => {
+ const endpoint = 'foo/bar/labels.json';
+ const singleQueryParams = '{"foo":"true"}';
+ const multipleQueryParams = '{"foo":"true","bar":"true"}';
+
+ expect(subject.getEndpointWithQueryParams(endpoint, singleQueryParams)).toBe(`${endpoint}?foo=true`);
+ expect(subject.getEndpointWithQueryParams(endpoint, multipleQueryParams)).toBe(`${endpoint}?foo=true&bar=true`);
+ });
+ });
+
describe('unselectTokens', () => {
it('does nothing when there are no tokens', () => {
const beforeHTML = tokensContainer.innerHTML;
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index 3adc29262f3..46c7b9f54f2 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -129,7 +129,7 @@ describe('AppComponent', () => {
vm.fetchGroups({});
setTimeout(() => {
- expect(vm.isLoading).toBeFalsy();
+ expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0);
expect(window.Flash).toHaveBeenCalledWith('An error occurred. Please try again.');
done();
@@ -144,10 +144,10 @@ describe('AppComponent', () => {
spyOn(vm, 'updateGroups').and.callThrough();
vm.fetchAllGroups();
- expect(vm.isLoading).toBeTruthy();
+ expect(vm.isLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalled();
setTimeout(() => {
- expect(vm.isLoading).toBeFalsy();
+ expect(vm.isLoading).toBe(false);
expect(vm.updateGroups).toHaveBeenCalled();
done();
}, 0);
@@ -181,7 +181,7 @@ describe('AppComponent', () => {
spyOn($, 'scrollTo');
vm.fetchPage(2, null, null, true);
- expect(vm.isLoading).toBeTruthy();
+ expect(vm.isLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalledWith({
page: 2,
filterGroupsBy: null,
@@ -190,7 +190,7 @@ describe('AppComponent', () => {
archived: true,
});
setTimeout(() => {
- expect(vm.isLoading).toBeFalsy();
+ expect(vm.isLoading).toBe(false);
expect($.scrollTo).toHaveBeenCalledWith(0);
expect(utils.mergeUrlParams).toHaveBeenCalledWith({ page: 2 }, jasmine.any(String));
expect(window.history.replaceState).toHaveBeenCalledWith({
@@ -216,7 +216,7 @@ describe('AppComponent', () => {
spyOn(vm.store, 'setGroupChildren');
vm.toggleChildren(groupItem);
- expect(groupItem.isChildrenLoading).toBeTruthy();
+ expect(groupItem.isChildrenLoading).toBe(true);
expect(vm.fetchGroups).toHaveBeenCalledWith({
parentId: groupItem.id,
});
@@ -232,7 +232,7 @@ describe('AppComponent', () => {
vm.toggleChildren(groupItem);
expect(vm.fetchGroups).not.toHaveBeenCalled();
- expect(groupItem.isOpen).toBeTruthy();
+ expect(groupItem.isOpen).toBe(true);
});
it('should collapse group if it is already expanded', () => {
@@ -241,16 +241,16 @@ describe('AppComponent', () => {
vm.toggleChildren(groupItem);
expect(vm.fetchGroups).not.toHaveBeenCalled();
- expect(groupItem.isOpen).toBeFalsy();
+ expect(groupItem.isOpen).toBe(false);
});
it('should set `isChildrenLoading` back to `false` if load request fails', (done) => {
spyOn(vm, 'fetchGroups').and.returnValue(returnServicePromise({}, true));
vm.toggleChildren(groupItem);
- expect(groupItem.isChildrenLoading).toBeTruthy();
+ expect(groupItem.isChildrenLoading).toBe(true);
setTimeout(() => {
- expect(groupItem.isChildrenLoading).toBeFalsy();
+ expect(groupItem.isChildrenLoading).toBe(false);
done();
}, 0);
});
@@ -268,10 +268,10 @@ describe('AppComponent', () => {
it('updates props which show modal confirmation dialog', () => {
const group = Object.assign({}, mockParentGroupItem);
- expect(vm.updateModal).toBeFalsy();
+ expect(vm.showModal).toBe(false);
expect(vm.groupLeaveConfirmationMessage).toBe('');
vm.showLeaveGroupModal(group, mockParentGroupItem);
- expect(vm.updateModal).toBeTruthy();
+ expect(vm.showModal).toBe(true);
expect(vm.groupLeaveConfirmationMessage).toBe(`Are you sure you want to leave the "${group.fullName}" group?`);
});
});
@@ -280,9 +280,9 @@ describe('AppComponent', () => {
it('hides modal confirmation which is shown before leaving the group', () => {
const group = Object.assign({}, mockParentGroupItem);
vm.showLeaveGroupModal(group, mockParentGroupItem);
- expect(vm.updateModal).toBeTruthy();
+ expect(vm.showModal).toBe(true);
vm.hideLeaveGroupModal();
- expect(vm.updateModal).toBeFalsy();
+ expect(vm.showModal).toBe(false);
});
});
@@ -307,8 +307,8 @@ describe('AppComponent', () => {
spyOn($, 'scrollTo');
vm.leaveGroup();
- expect(vm.updateModal).toBeFalsy();
- expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.showModal).toBe(false);
+ expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(vm.targetGroup.leavePath);
setTimeout(() => {
expect($.scrollTo).toHaveBeenCalledWith(0);
@@ -325,12 +325,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash');
vm.leaveGroup();
- expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBe(false);
done();
}, 0);
});
@@ -342,12 +342,12 @@ describe('AppComponent', () => {
spyOn(window, 'Flash');
vm.leaveGroup(childGroupItem, groupItem);
- expect(vm.targetGroup.isBeingRemoved).toBeTruthy();
+ expect(vm.targetGroup.isBeingRemoved).toBe(true);
expect(vm.service.leaveGroup).toHaveBeenCalledWith(childGroupItem.leavePath);
setTimeout(() => {
expect(vm.store.removeGroup).not.toHaveBeenCalled();
expect(window.Flash).toHaveBeenCalledWith(message);
- expect(vm.targetGroup.isBeingRemoved).toBeFalsy();
+ expect(vm.targetGroup.isBeingRemoved).toBe(false);
done();
}, 0);
});
@@ -379,10 +379,10 @@ describe('AppComponent', () => {
it('should set `isSearchEmpty` prop based on groups count', () => {
vm.updateGroups(mockGroups);
- expect(vm.isSearchEmpty).toBeFalsy();
+ expect(vm.isSearchEmpty).toBe(false);
vm.updateGroups([]);
- expect(vm.isSearchEmpty).toBeTruthy();
+ expect(vm.isSearchEmpty).toBe(true);
});
});
});
@@ -473,13 +473,16 @@ describe('AppComponent', () => {
});
});
- it('renders modal confirmation dialog', () => {
+ it('renders modal confirmation dialog', (done) => {
vm.groupLeaveConfirmationMessage = 'Are you sure you want to leave the "foo" group?';
- vm.updateModal = true;
- const modalDialogEl = vm.$el.querySelector('.modal');
- expect(modalDialogEl).not.toBe(null);
- expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
- expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+ vm.showModal = true;
+ Vue.nextTick(() => {
+ const modalDialogEl = vm.$el.querySelector('.modal');
+ expect(modalDialogEl).not.toBe(null);
+ expect(modalDialogEl.querySelector('.modal-title').innerText.trim()).toBe('Are you sure?');
+ expect(modalDialogEl.querySelector('.btn.btn-warning').innerText.trim()).toBe('Leave');
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index 49799c31995..27f06573432 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -166,6 +166,21 @@ describe('common_utils', () => {
});
});
+ describe('objectToQueryString', () => {
+ it('returns empty string when `param` is undefined, null or empty string', () => {
+ expect(commonUtils.objectToQueryString()).toBe('');
+ expect(commonUtils.objectToQueryString('')).toBe('');
+ });
+
+ it('returns query string with values of `params`', () => {
+ const singleQueryParams = { foo: true };
+ const multipleQueryParams = { foo: true, bar: true };
+
+ expect(commonUtils.objectToQueryString(singleQueryParams)).toBe('foo=true');
+ expect(commonUtils.objectToQueryString(multipleQueryParams)).toBe('foo=true&bar=true');
+ });
+ });
+
describe('buildUrlWithCurrentLocation', () => {
it('should build an url with current location and given parameters', () => {
expect(commonUtils.buildUrlWithCurrentLocation()).toEqual(window.location.pathname);
diff --git a/spec/javascripts/pipelines/blank_state_spec.js b/spec/javascripts/pipelines/blank_state_spec.js
new file mode 100644
index 00000000000..b7a9b60d85c
--- /dev/null
+++ b/spec/javascripts/pipelines/blank_state_spec.js
@@ -0,0 +1,29 @@
+import Vue from 'vue';
+import component from '~/pipelines/components/blank_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
+
+describe('Pipelines Blank State', () => {
+ let vm;
+ let Component;
+
+ beforeEach(() => {
+ Component = Vue.extend(component);
+
+ vm = mountComponent(Component,
+ {
+ svgPath: 'foo',
+ message: 'Blank State',
+ },
+ );
+ });
+
+ it('should render svg', () => {
+ expect(vm.$el.querySelector('.svg-content img').getAttribute('src')).toEqual('foo');
+ });
+
+ it('should render message', () => {
+ expect(
+ vm.$el.querySelector('h4').textContent.trim(),
+ ).toEqual('Blank State');
+ });
+});
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/javascripts/pipelines/empty_state_spec.js
index 97f04844b3a..71f77e5f42e 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/javascripts/pipelines/empty_state_spec.js
@@ -1,5 +1,6 @@
import Vue from 'vue';
import emptyStateComp from '~/pipelines/components/empty_state.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Empty State', () => {
let component;
@@ -8,12 +9,15 @@ describe('Pipelines Empty State', () => {
beforeEach(() => {
EmptyStateComponent = Vue.extend(emptyStateComp);
- component = new EmptyStateComponent({
- propsData: {
- helpPagePath: 'foo',
- emptyStateSvgPath: 'foo',
- },
- }).$mount();
+ component = mountComponent(EmptyStateComponent, {
+ helpPagePath: 'foo',
+ emptyStateSvgPath: 'foo',
+ canSetCi: true,
+ });
+ });
+
+ afterEach(() => {
+ component.$destroy();
});
it('should render empty state SVG', () => {
@@ -24,16 +28,16 @@ describe('Pipelines Empty State', () => {
expect(component.$el.querySelector('h4').textContent).toContain('Build with confidence');
expect(
- component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
- ).toContain('Continous Integration can help catch bugs by running your tests automatically');
+ component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
+ ).toContain('Continous Integration can help catch bugs by running your tests automatically,');
expect(
- component.$el.querySelector('p').textContent.trim().replace(/[\r\n]+/g, ' '),
- ).toContain('Continuous Deployment can help you deliver code to your product environment');
+ component.$el.querySelector('p').innerHTML.trim().replace(/\n+\s+/m, ' ').replace(/\s\s+/g, ' '),
+ ).toContain('while Continuous Deployment can help you deliver code to your product environment');
});
it('should render a link with provided help path', () => {
- expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual('foo');
- expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
+ expect(component.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual('foo');
+ expect(component.$el.querySelector('.js-get-started-pipelines').textContent).toContain('Get started with Pipelines');
});
});
diff --git a/spec/javascripts/pipelines/error_state_spec.js b/spec/javascripts/pipelines/error_state_spec.js
deleted file mode 100644
index a402857a4d1..00000000000
--- a/spec/javascripts/pipelines/error_state_spec.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import Vue from 'vue';
-import errorStateComp from '~/pipelines/components/error_state.vue';
-
-describe('Pipelines Error State', () => {
- let component;
- let ErrorStateComponent;
-
- beforeEach(() => {
- ErrorStateComponent = Vue.extend(errorStateComp);
-
- component = new ErrorStateComponent({
- propsData: {
- errorStateSvgPath: 'foo',
- },
- }).$mount();
- });
-
- it('should render error state SVG', () => {
- expect(component.$el.querySelector('.svg-content svg')).toBeDefined();
- });
-
- it('should render emtpy state information', () => {
- expect(
- component.$el.querySelector('h4').textContent,
- ).toContain('The API failed to fetch the pipelines');
- });
-});
diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js
index 09a0c14d96c..77c5258f74c 100644
--- a/spec/javascripts/pipelines/nav_controls_spec.js
+++ b/spec/javascripts/pipelines/nav_controls_spec.js
@@ -1,116 +1,68 @@
import Vue from 'vue';
import navControlsComp from '~/pipelines/components/nav_controls.vue';
+import mountComponent from '../helpers/vue_mount_component_helper';
describe('Pipelines Nav Controls', () => {
let NavControlsComponent;
+ let component;
beforeEach(() => {
NavControlsComponent = Vue.extend(navControlsComp);
});
+ afterEach(() => {
+ component.$destroy();
+ });
+
it('should render link to create a new pipeline', () => {
const mockData = {
newPipelinePath: 'foo',
- hasCiEnabled: true,
- helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
- canCreatePipeline: true,
};
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
+ component = mountComponent(NavControlsComponent, mockData);
- expect(component.$el.querySelector('.btn-create').textContent).toContain('Run Pipeline');
- expect(component.$el.querySelector('.btn-create').getAttribute('href')).toEqual(mockData.newPipelinePath);
+ expect(component.$el.querySelector('.js-run-pipeline').textContent).toContain('Run Pipeline');
+ expect(component.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(mockData.newPipelinePath);
});
- it('should not render link to create pipeline if no permission is provided', () => {
+ it('should not render link to create pipeline if no path is provided', () => {
const mockData = {
- newPipelinePath: 'foo',
- hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
- canCreatePipeline: false,
};
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
+ component = mountComponent(NavControlsComponent, mockData);
- expect(component.$el.querySelector('.btn-create')).toEqual(null);
+ expect(component.$el.querySelector('.js-run-pipeline')).toEqual(null);
});
it('should render link for resetting runner caches', () => {
const mockData = {
newPipelinePath: 'foo',
- hasCiEnabled: true,
- helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
- canCreatePipeline: false,
};
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
+ component = mountComponent(NavControlsComponent, mockData);
- expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches');
- expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath);
+ expect(component.$el.querySelector('.js-clear-cache').textContent.trim()).toContain('Clear Runner Caches');
+ expect(component.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(mockData.resetCachePath);
});
it('should render link for CI lint', () => {
const mockData = {
newPipelinePath: 'foo',
- hasCiEnabled: true,
- helpPagePath: 'foo',
- ciLintPath: 'foo',
- resetCachePath: 'foo',
- canCreatePipeline: true,
- };
-
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
-
- expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint');
- expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath);
- });
-
- it('should render link to help page when CI is not enabled', () => {
- const mockData = {
- newPipelinePath: 'foo',
- hasCiEnabled: false,
- helpPagePath: 'foo',
- ciLintPath: 'foo',
- resetCachePath: 'foo',
- canCreatePipeline: true,
- };
-
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
-
- expect(component.$el.querySelector('.btn-info').textContent).toContain('Get started with Pipelines');
- expect(component.$el.querySelector('.btn-info').getAttribute('href')).toEqual(mockData.helpPagePath);
- });
-
- it('should not render link to help page when CI is enabled', () => {
- const mockData = {
- newPipelinePath: 'foo',
- hasCiEnabled: true,
helpPagePath: 'foo',
ciLintPath: 'foo',
resetCachePath: 'foo',
- canCreatePipeline: true,
};
- const component = new NavControlsComponent({
- propsData: mockData,
- }).$mount();
+ component = mountComponent(NavControlsComponent, mockData);
- expect(component.$el.querySelector('.btn-info')).toEqual(null);
+ expect(component.$el.querySelector('.js-ci-lint').textContent.trim()).toContain('CI Lint');
+ expect(component.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(mockData.ciLintPath);
});
});
diff --git a/spec/javascripts/pipelines/pipelines_spec.js b/spec/javascripts/pipelines/pipelines_spec.js
index 54d5bfd51e6..84fd0329f08 100644
--- a/spec/javascripts/pipelines/pipelines_spec.js
+++ b/spec/javascripts/pipelines/pipelines_spec.js
@@ -7,36 +7,380 @@ import mountComponent from 'spec/helpers/vue_mount_component_helper';
describe('Pipelines', () => {
const jsonFixtureName = 'pipelines/pipelines.json';
- preloadFixtures('static/pipelines.html.raw');
preloadFixtures(jsonFixtureName);
let PipelinesComponent;
let pipelines;
- let component;
+ let vm;
+ const paths = {
+ endpoint: 'twitter/flight/pipelines.json',
+ autoDevopsPath: '/help/topics/autodevops/index.md',
+ helpPagePath: '/help/ci/quick_start/README',
+ emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+ errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
+ noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
+ ciLintPath: '/ci/lint',
+ resetCachePath: '/twitter/flight/settings/ci_cd/reset_cache',
+ newPipelinePath: '/twitter/flight/pipelines/new',
+ };
+
+ const noPermissions = {
+ endpoint: 'twitter/flight/pipelines.json',
+ autoDevopsPath: '/help/topics/autodevops/index.md',
+ helpPagePath: '/help/ci/quick_start/README',
+ emptyStateSvgPath: '/assets/illustrations/pipelines_empty.svg',
+ errorStateSvgPath: '/assets/illustrations/pipelines_failed.svg',
+ noPipelinesSvgPath: '/assets/illustrations/pipelines_pending.svg',
+ };
beforeEach(() => {
- loadFixtures('static/pipelines.html.raw');
pipelines = getJSONFixture(jsonFixtureName);
PipelinesComponent = Vue.extend(pipelinesComp);
});
afterEach(() => {
- component.$destroy();
+ vm.$destroy();
+ });
+
+ const pipelinesInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify(pipelines), {
+ status: 200,
+ }));
+ };
+
+ const emptyStateInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({
+ pipelines: [],
+ count: {
+ all: 0,
+ pending: 0,
+ running: 0,
+ finished: 0,
+ },
+ }), {
+ status: 200,
+ }));
+ };
+
+ const errorInterceptor = (request, next) => {
+ next(request.respondWith(JSON.stringify({}), {
+ status: 500,
+ }));
+ };
+
+ describe('With permission', () => {
+ describe('With pipelines in main tab', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(pipelinesInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, pipelinesInterceptor,
+ );
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('renders Run Pipeline button', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+ });
+
+ it('renders CI Lint button', () => {
+ expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+ });
+
+ it('renders Clear Runner Cache button', () => {
+ expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ });
+
+ it('renders pipelines table', () => {
+ expect(
+ vm.$el.querySelectorAll('.gl-responsive-table-row').length,
+ ).toEqual(pipelines.pipelines.length + 1);
+ });
+ });
+
+ describe('Without pipelines on main tab with CI', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(emptyStateInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, emptyStateInterceptor,
+ );
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('renders Run Pipeline button', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+ });
+
+ it('renders CI Lint button', () => {
+ expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+ });
+
+ it('renders Clear Runner Cache button', () => {
+ expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ });
+
+ it('renders tab empty state', () => {
+ expect(vm.$el.querySelector('.empty-state h4').textContent.trim()).toEqual('There are currently no pipelines.');
+ });
+ });
+
+ describe('Without pipelines nor CI', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(emptyStateInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: false,
+ canCreatePipeline: true,
+ ...paths,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, emptyStateInterceptor,
+ );
+ });
+
+ it('renders empty state', () => {
+ expect(vm.$el.querySelector('.js-empty-state h4').textContent.trim()).toEqual('Build with confidence');
+ expect(vm.$el.querySelector('.js-get-started-pipelines').getAttribute('href')).toEqual(paths.helpPagePath);
+ });
+
+ it('does not render tabs nor buttons', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull();
+ expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+ expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+ expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+ });
+ });
+
+ describe('When API returns error', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(errorInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: false,
+ canCreatePipeline: true,
+ ...paths,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, errorInterceptor,
+ );
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('renders buttons', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline').getAttribute('href')).toEqual(paths.newPipelinePath);
+ expect(vm.$el.querySelector('.js-ci-lint').getAttribute('href')).toEqual(paths.ciLintPath);
+ expect(vm.$el.querySelector('.js-clear-cache').getAttribute('href')).toEqual(paths.resetCachePath);
+ });
+
+ it('renders error state', () => {
+ expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.');
+ });
+ });
+ });
+
+ describe('Without permission', () => {
+ describe('With pipelines in main tab', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(pipelinesInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: false,
+ canCreatePipeline: false,
+ ...noPermissions,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, pipelinesInterceptor,
+ );
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('does not render buttons', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+ expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+ expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+ });
+
+ it('renders pipelines table', () => {
+ expect(
+ vm.$el.querySelectorAll('.gl-responsive-table-row').length,
+ ).toEqual(pipelines.pipelines.length + 1);
+ });
+ });
+
+ describe('Without pipelines on main tab with CI', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(emptyStateInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: false,
+ ...noPermissions,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, emptyStateInterceptor,
+ );
+ });
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('does not render buttons', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+ expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+ expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+ });
+
+ it('renders tab empty state', () => {
+ expect(vm.$el.querySelector('.empty-state h4').textContent.trim()).toEqual('There are currently no pipelines.');
+ });
+ });
+
+ describe('Without pipelines nor CI', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(emptyStateInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: false,
+ canCreatePipeline: false,
+ ...noPermissions,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, emptyStateInterceptor,
+ );
+ });
+
+ it('renders empty state without button to set CI', () => {
+ expect(vm.$el.querySelector('.js-empty-state').textContent.trim()).toEqual('This project is not currently set up to run pipelines.');
+ expect(vm.$el.querySelector('.js-get-started-pipelines')).toBeNull();
+ });
+
+ it('does not render tabs or buttons', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all')).toBeNull();
+ expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+ expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+ expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+ });
+ });
+
+ describe('When API returns error', () => {
+ beforeEach((done) => {
+ Vue.http.interceptors.push(errorInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: false,
+ canCreatePipeline: true,
+ ...noPermissions,
+ });
+
+ setTimeout(() => {
+ done();
+ });
+ });
+
+ afterEach(() => {
+ Vue.http.interceptors = _.without(
+ Vue.http.interceptors, errorInterceptor,
+ );
+ });
+
+ it('renders tabs', () => {
+ expect(vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim()).toContain('All');
+ });
+
+ it('does not renders buttons', () => {
+ expect(vm.$el.querySelector('.js-run-pipeline')).toBeNull();
+ expect(vm.$el.querySelector('.js-ci-lint')).toBeNull();
+ expect(vm.$el.querySelector('.js-clear-cache')).toBeNull();
+ });
+
+ it('renders error state', () => {
+ expect(vm.$el.querySelector('.empty-state').textContent.trim()).toContain('There was an error fetching the pipelines.');
+ });
+ });
});
describe('successfull request', () => {
describe('with pipelines', () => {
- const pipelinesInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify(pipelines), {
- status: 200,
- }));
- };
-
beforeEach(() => {
Vue.http.interceptors.push(pipelinesInterceptor);
- component = mountComponent(PipelinesComponent, {
+ vm = mountComponent(PipelinesComponent, {
store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
});
});
@@ -48,9 +392,9 @@ describe('Pipelines', () => {
it('should render table', (done) => {
setTimeout(() => {
- expect(component.$el.querySelector('.table-holder')).toBeDefined();
+ expect(vm.$el.querySelector('.table-holder')).toBeDefined();
expect(
- component.$el.querySelectorAll('.gl-responsive-table-row').length,
+ vm.$el.querySelectorAll('.gl-responsive-table-row').length,
).toEqual(pipelines.pipelines.length + 1);
done();
});
@@ -59,22 +403,22 @@ describe('Pipelines', () => {
it('should render navigation tabs', (done) => {
setTimeout(() => {
expect(
- component.$el.querySelector('.js-pipelines-tab-pending').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-pending').textContent.trim(),
).toContain('Pending');
expect(
- component.$el.querySelector('.js-pipelines-tab-all').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-all').textContent.trim(),
).toContain('All');
expect(
- component.$el.querySelector('.js-pipelines-tab-running').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-running').textContent.trim(),
).toContain('Running');
expect(
- component.$el.querySelector('.js-pipelines-tab-finished').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-finished').textContent.trim(),
).toContain('Finished');
expect(
- component.$el.querySelector('.js-pipelines-tab-branches').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-branches').textContent.trim(),
).toContain('Branches');
expect(
- component.$el.querySelector('.js-pipelines-tab-tags').textContent.trim(),
+ vm.$el.querySelector('.js-pipelines-tab-tags').textContent.trim(),
).toContain('Tags');
done();
});
@@ -82,10 +426,10 @@ describe('Pipelines', () => {
it('should make an API request when using tabs', (done) => {
setTimeout(() => {
- spyOn(component, 'updateContent');
- component.$el.querySelector('.js-pipelines-tab-finished').click();
+ spyOn(vm, 'updateContent');
+ vm.$el.querySelector('.js-pipelines-tab-finished').click();
- expect(component.updateContent).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
+ expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'finished', page: '1' });
done();
});
});
@@ -93,9 +437,9 @@ describe('Pipelines', () => {
describe('with pagination', () => {
it('should make an API request when using pagination', (done) => {
setTimeout(() => {
- spyOn(component, 'updateContent');
+ spyOn(vm, 'updateContent');
// Mock pagination
- component.store.state.pageInfo = {
+ vm.store.state.pageInfo = {
page: 1,
total: 10,
perPage: 2,
@@ -103,9 +447,9 @@ describe('Pipelines', () => {
totalPages: 5,
};
- Vue.nextTick(() => {
- component.$el.querySelector('.js-next-button a').click();
- expect(component.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
+ vm.$nextTick(() => {
+ vm.$el.querySelector('.js-next-button a').click();
+ expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'all', page: '2' });
done();
});
@@ -113,112 +457,249 @@ describe('Pipelines', () => {
});
});
});
+ });
- describe('without pipelines', () => {
- const emptyInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 200,
- }));
- };
+ describe('methods', () => {
+ beforeEach(() => {
+ spyOn(history, 'pushState').and.stub();
+ });
- beforeEach(() => {
- Vue.http.interceptors.push(emptyInterceptor);
- });
+ describe('updateContent', () => {
+ it('should set given parameters', () => {
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
+ });
+ vm.updateContent({ scope: 'finished', page: '4' });
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, emptyInterceptor,
- );
+ expect(vm.page).toEqual('4');
+ expect(vm.scope).toEqual('finished');
+ expect(vm.requestData.scope).toEqual('finished');
+ expect(vm.requestData.page).toEqual('4');
});
+ });
+
+ describe('onChangeTab', () => {
+ it('should set page to 1', () => {
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
+ });
+ spyOn(vm, 'updateContent');
- it('should render empty state', (done) => {
- component = new PipelinesComponent({
- propsData: {
- store: new Store(),
- },
- }).$mount();
+ vm.onChangeTab('running');
- setTimeout(() => {
- expect(component.$el.querySelector('.empty-state')).not.toBe(null);
- done();
+ expect(vm.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+ });
+ });
+
+ describe('onChangePage', () => {
+ it('should update page and keep scope', () => {
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
});
+ spyOn(vm, 'updateContent');
+
+ vm.onChangePage(4);
+
+ expect(vm.updateContent).toHaveBeenCalledWith({ scope: vm.scope, page: '4' });
});
});
});
- describe('unsuccessfull request', () => {
- const errorInterceptor = (request, next) => {
- next(request.respondWith(JSON.stringify([]), {
- status: 500,
- }));
- };
-
+ describe('computed properties', () => {
beforeEach(() => {
- Vue.http.interceptors.push(errorInterceptor);
+ vm = mountComponent(PipelinesComponent, {
+ store: new Store(),
+ hasGitlabCi: true,
+ canCreatePipeline: true,
+ ...paths,
+ });
});
- afterEach(() => {
- Vue.http.interceptors = _.without(
- Vue.http.interceptors, errorInterceptor,
- );
+ describe('tabs', () => {
+ it('returns default tabs', () => {
+ expect(vm.tabs).toEqual([
+ { name: 'All', scope: 'all', count: undefined, isActive: true },
+ { name: 'Pending', scope: 'pending', count: undefined, isActive: false },
+ { name: 'Running', scope: 'running', count: undefined, isActive: false },
+ { name: 'Finished', scope: 'finished', count: undefined, isActive: false },
+ { name: 'Branches', scope: 'branches', isActive: false },
+ { name: 'Tags', scope: 'tags', isActive: false },
+ ]);
+ });
});
- it('should render error state', (done) => {
- component = new PipelinesComponent({
- propsData: {
- store: new Store(),
- },
- }).$mount();
+ describe('emptyTabMessage', () => {
+ it('returns message with scope', (done) => {
+ vm.scope = 'pending';
- setTimeout(() => {
- expect(component.$el.querySelector('.js-pipelines-error-state')).toBeDefined();
- done();
+ vm.$nextTick(() => {
+ expect(vm.emptyTabMessage).toEqual('There are currently no pending pipelines.');
+ done();
+ });
});
- });
- });
- describe('methods', () => {
- beforeEach(() => {
- spyOn(history, 'pushState').and.stub();
+ it('returns message without scope when scope is `all`', () => {
+ expect(vm.emptyTabMessage).toEqual('There are currently no pipelines.');
+ });
});
- describe('updateContent', () => {
- it('should set given parameters', () => {
- component = mountComponent(PipelinesComponent, {
- store: new Store(),
+ describe('stateToRender', () => {
+ it('returns loading state when the app is loading', () => {
+ expect(vm.stateToRender).toEqual('loading');
+ });
+
+ it('returns error state when app has error', (done) => {
+ vm.hasError = true;
+ vm.isLoading = false;
+
+ vm.$nextTick(() => {
+ expect(vm.stateToRender).toEqual('error');
+ done();
+ });
+ });
+
+ it('returns table list when app has pipelines', (done) => {
+ vm.isLoading = false;
+ vm.hasError = false;
+ vm.state.pipelines = pipelines.pipelines;
+
+ vm.$nextTick(() => {
+ expect(vm.stateToRender).toEqual('tableList');
+
+ done();
+ });
+ });
+
+ it('returns empty tab when app does not have pipelines but project has pipelines', (done) => {
+ vm.state.count.all = 10;
+ vm.isLoading = false;
+
+ vm.$nextTick(() => {
+ expect(vm.stateToRender).toEqual('emptyTab');
+
+ done();
});
- component.updateContent({ scope: 'finished', page: '4' });
+ });
+
+ it('returns empty tab when project has CI', (done) => {
+ vm.isLoading = false;
+ vm.$nextTick(() => {
+ expect(vm.stateToRender).toEqual('emptyTab');
- expect(component.page).toEqual('4');
- expect(component.scope).toEqual('finished');
- expect(component.requestData.scope).toEqual('finished');
- expect(component.requestData.page).toEqual('4');
+ done();
+ });
+ });
+
+ it('returns empty state when project does not have pipelines nor CI', (done) => {
+ vm.isLoading = false;
+ vm.hasGitlabCi = false;
+ vm.$nextTick(() => {
+ expect(vm.stateToRender).toEqual('emptyState');
+
+ done();
+ });
});
});
- describe('onChangeTab', () => {
- it('should set page to 1', () => {
- component = mountComponent(PipelinesComponent, {
- store: new Store(),
+ describe('shouldRenderTabs', () => {
+ it('returns true when state is loading & has already made the first request', (done) => {
+ vm.isLoading = true;
+ vm.hasMadeRequest = true;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(true);
+
+ done();
});
- spyOn(component, 'updateContent');
+ });
- component.onChangeTab('running');
+ it('returns true when state is tableList & has already made the first request', (done) => {
+ vm.isLoading = false;
+ vm.state.pipelines = pipelines.pipelines;
+ vm.hasMadeRequest = true;
- expect(component.updateContent).toHaveBeenCalledWith({ scope: 'running', page: '1' });
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(true);
+
+ done();
+ });
+ });
+
+ it('returns true when state is error & has already made the first request', (done) => {
+ vm.isLoading = false;
+ vm.hasError = true;
+ vm.hasMadeRequest = true;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(true);
+
+ done();
+ });
+ });
+
+ it('returns true when state is empty tab & has already made the first request', (done) => {
+ vm.isLoading = false;
+ vm.state.count.all = 10;
+ vm.hasMadeRequest = true;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(true);
+
+ done();
+ });
+ });
+
+ it('returns false when has not made first request', (done) => {
+ vm.hasMadeRequest = false;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(false);
+
+ done();
+ });
+ });
+
+ it('returns false when state is emtpy state', (done) => {
+ vm.isLoading = false;
+ vm.hasMadeRequest = true;
+ vm.hasGitlabCi = false;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderTabs).toEqual(false);
+
+ done();
+ });
});
});
- describe('onChangePage', () => {
- it('should update page and keep scope', () => {
- component = mountComponent(PipelinesComponent, {
- store: new Store(),
+ describe('shouldRenderButtons', () => {
+ it('returns true when it has paths & has made the first request', (done) => {
+ vm.hasMadeRequest = true;
+
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderButtons).toEqual(true);
+
+ done();
});
- spyOn(component, 'updateContent');
+ });
+
+ it('returns false when it has not made the first request', (done) => {
+ vm.hasMadeRequest = false;
- component.onChangePage(4);
+ vm.$nextTick(() => {
+ expect(vm.shouldRenderButtons).toEqual(false);
- expect(component.updateContent).toHaveBeenCalledWith({ scope: component.scope, page: '4' });
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
deleted file mode 100644
index b509cedbe80..00000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_collapsed_spec.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list collapsed', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(listCollapsed);
-
- vm = createComponentWithStore(Component, store);
-
- vm.$store.state.openFiles.push(file('file1'), file('file2'));
- vm.$store.state.openFiles[0].tempFile = true;
- vm.$store.state.openFiles.forEach((f) => {
- Object.assign(f, {
- changed: true,
- });
- });
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders added & modified files count', () => {
- expect(vm.$el.textContent.replace(/\s+/g, ' ').trim()).toBe('1 1');
- });
-});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
deleted file mode 100644
index 6f1a1d874d3..00000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_item_spec.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import Vue from 'vue';
-import listItem from '~/ide/components/commit_sidebar/list_item.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list item', () => {
- let vm;
- let f;
-
- beforeEach(() => {
- const Component = Vue.extend(listItem);
-
- f = file('test-file');
-
- vm = mountComponent(Component, {
- file: f,
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders file path', () => {
- expect(vm.$el.querySelector('.multi-file-commit-list-path').textContent.trim()).toBe(f.path);
- });
-
- describe('computed', () => {
- describe('iconName', () => {
- it('returns modified when not a tempFile', () => {
- expect(vm.iconName).toBe('file-modified');
- });
-
- it('returns addition when not a tempFile', () => {
- f.tempFile = true;
-
- expect(vm.iconName).toBe('file-addition');
- });
- });
-
- describe('iconClass', () => {
- it('returns modified when not a tempFile', () => {
- expect(vm.iconClass).toContain('multi-file-modified');
- });
-
- it('returns addition when not a tempFile', () => {
- f.tempFile = true;
-
- expect(vm.iconClass).toContain('multi-file-addition');
- });
- });
- });
-});
diff --git a/spec/javascripts/repo/components/commit_sidebar/list_spec.js b/spec/javascripts/repo/components/commit_sidebar/list_spec.js
deleted file mode 100644
index aeb9de9ace4..00000000000
--- a/spec/javascripts/repo/components/commit_sidebar/list_spec.js
+++ /dev/null
@@ -1,59 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import commitSidebarList from '~/ide/components/commit_sidebar/list.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file } from '../../helpers';
-
-describe('Multi-file editor commit sidebar list', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(commitSidebarList);
-
- vm = createComponentWithStore(Component, store, {
- title: 'Staged',
- fileList: [],
- });
-
- vm.$store.state.rightPanelCollapsed = false;
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('empty file list', () => {
- it('renders no changes text', () => {
- expect(vm.$el.querySelector('.help-block').textContent.trim()).toBe('No changes');
- });
- });
-
- describe('with a list of files', () => {
- beforeEach((done) => {
- const f = file('file name');
- f.changed = true;
- vm.fileList.push(f);
-
- Vue.nextTick(done);
- });
-
- it('renders list', () => {
- expect(vm.$el.querySelectorAll('li').length).toBe(1);
- });
- });
-
- describe('collapsed', () => {
- beforeEach((done) => {
- vm.$store.state.rightPanelCollapsed = true;
-
- Vue.nextTick(done);
- });
-
- it('hides list', () => {
- expect(vm.$el.querySelector('.list-unstyled')).toBeNull();
- expect(vm.$el.querySelector('.help-block')).toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/repo/components/ide_context_bar_spec.js b/spec/javascripts/repo/components/ide_context_bar_spec.js
deleted file mode 100644
index 935da259a99..00000000000
--- a/spec/javascripts/repo/components/ide_context_bar_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideContextBar from '~/ide/components/ide_context_bar.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-
-describe('Multi-file editor right context bar', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(ideContextBar);
-
- vm = createComponentWithStore(Component, store);
-
- vm.$store.state.rightPanelCollapsed = false;
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('collapsed', () => {
- beforeEach((done) => {
- vm.$store.state.rightPanelCollapsed = true;
-
- Vue.nextTick(done);
- });
-
- it('adds collapsed class', () => {
- expect(vm.$el.querySelector('.is-collapsed')).not.toBeNull();
- });
-
- it('shows correct icon', () => {
- expect(vm.currentIcon).toBe('angle-double-left');
- });
- });
-
- it('clicking toggle collapse button collapses the bar', () => {
- spyOn(vm, 'setPanelCollapsedStatus').and.returnValue(Promise.resolve());
-
- vm.$el.querySelector('.multi-file-commit-panel-collapse-btn').click();
-
- expect(vm.setPanelCollapsedStatus).toHaveBeenCalledWith({
- side: 'right',
- collapsed: true,
- });
- });
-});
diff --git a/spec/javascripts/repo/components/ide_repo_tree_spec.js b/spec/javascripts/repo/components/ide_repo_tree_spec.js
deleted file mode 100644
index e3bbda514da..00000000000
--- a/spec/javascripts/repo/components/ide_repo_tree_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideRepoTree from '~/ide/components/ide_repo_tree.vue';
-import { file, resetStore } from '../helpers';
-
-describe('IdeRepoTree', () => {
- let vm;
-
- beforeEach(() => {
- const IdeRepoTree = Vue.extend(ideRepoTree);
-
- vm = new IdeRepoTree({
- store,
- propsData: {
- treeId: 'abcproject/mybranch',
- },
- });
-
- vm.$store.state.currentBranch = 'master';
- vm.$store.state.isRoot = true;
- vm.$store.state.trees['abcproject/mybranch'] = {
- tree: [file()],
- };
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders a sidebar', () => {
- const tbody = vm.$el.querySelector('tbody');
-
- expect(vm.$el.classList.contains('sidebar-mini')).toBeFalsy();
- expect(tbody.querySelector('.repo-file-options')).toBeFalsy();
- expect(tbody.querySelector('.prev-directory')).toBeFalsy();
- expect(tbody.querySelector('.loading-file')).toBeFalsy();
- expect(tbody.querySelector('.file')).toBeTruthy();
- });
-
- it('renders 3 loading files if tree is loading', (done) => {
- vm.treeId = '123';
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelectorAll('.multi-file-loading-container').length).toEqual(3);
-
- done();
- });
- });
-
- it('renders a prev directory if is not root', (done) => {
- vm.$store.state.isRoot = false;
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('tbody .prev-directory')).toBeTruthy();
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/repo/components/ide_side_bar_spec.js b/spec/javascripts/repo/components/ide_side_bar_spec.js
deleted file mode 100644
index 79c3c8128e8..00000000000
--- a/spec/javascripts/repo/components/ide_side_bar_spec.js
+++ /dev/null
@@ -1,43 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ideSidebar from '~/ide/components/ide_side_bar.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../helpers';
-
-describe('IdeSidebar', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(ideSidebar);
-
- vm = createComponentWithStore(Component, store).$mount();
-
- vm.$store.state.leftPanelCollapsed = false;
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders a sidebar', () => {
- expect(vm.$el.querySelector('.multi-file-commit-panel-inner')).not.toBeNull();
- });
-
- describe('collapsed', () => {
- beforeEach((done) => {
- vm.$store.state.leftPanelCollapsed = true;
-
- Vue.nextTick(done);
- });
-
- it('adds collapsed class', () => {
- expect(vm.$el.classList).toContain('is-collapsed');
- });
-
- it('shows correct icon', () => {
- expect(vm.currentIcon).toBe('angle-double-right');
- });
- });
-});
diff --git a/spec/javascripts/repo/components/ide_spec.js b/spec/javascripts/repo/components/ide_spec.js
deleted file mode 100644
index 18135177b5e..00000000000
--- a/spec/javascripts/repo/components/ide_spec.js
+++ /dev/null
@@ -1,39 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import ide from '~/ide/components/ide.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file, resetStore } from '../helpers';
-
-describe('ide component', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(ide);
-
- vm = createComponentWithStore(Component, store, {
- emptyStateSvgPath: 'svg',
- }).$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('does not render panel right when no files open', () => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
- });
-
- it('renders panel right when files are open', (done) => {
- vm.$store.state.trees['abcproject/mybranch'] = {
- tree: [file()],
- };
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.panel-right')).toBeNull();
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/repo/components/new_branch_form_spec.js b/spec/javascripts/repo/components/new_branch_form_spec.js
deleted file mode 100644
index 82597fc75e8..00000000000
--- a/spec/javascripts/repo/components/new_branch_form_spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import newBranchForm from '~/ide/components/new_branch_form.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../helpers';
-
-describe('Multi-file editor new branch form', () => {
- let vm;
-
- beforeEach(() => {
- const Component = Vue.extend(newBranchForm);
-
- vm = createComponentWithStore(Component, store);
-
- vm.$store.state.currentBranch = 'master';
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- describe('template', () => {
- it('renders submit as disabled', () => {
- expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBe('disabled');
- });
-
- it('enables the submit button when branch is not empty', (done) => {
- vm.branchName = 'testing';
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn').getAttribute('disabled')).toBeNull();
-
- done();
- });
- });
-
- it('displays current branch creating from', (done) => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('p').textContent.replace(/\s+/g, ' ').trim()).toBe('Create from: master');
-
- done();
- });
- });
- });
-
- describe('submitNewBranch', () => {
- beforeEach(() => {
- spyOn(vm, 'createNewBranch').and.returnValue(Promise.resolve());
- });
-
- it('sets to loading', () => {
- vm.submitNewBranch();
-
- expect(vm.loading).toBeTruthy();
- });
-
- it('hides current flash element', (done) => {
- vm.$refs.flashContainer.innerHTML = '<div class="flash-alert"></div>';
-
- vm.submitNewBranch();
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.flash-alert')).toBeNull();
-
- done();
- });
- });
-
- it('calls createdNewBranch with branchName', () => {
- vm.branchName = 'testing';
-
- vm.submitNewBranch();
-
- expect(vm.createNewBranch).toHaveBeenCalledWith('testing');
- });
- });
-
- describe('submitNewBranch with error', () => {
- beforeEach(() => {
- spyOn(vm, 'createNewBranch').and.returnValue(Promise.reject({
- json: () => Promise.resolve({
- message: 'error message',
- }),
- }));
- });
-
- it('sets loading to false', (done) => {
- vm.loading = true;
-
- vm.submitNewBranch();
-
- setTimeout(() => {
- expect(vm.loading).toBeFalsy();
-
- done();
- });
- });
-
- it('creates flash element', (done) => {
- vm.submitNewBranch();
-
- setTimeout(() => {
- expect(vm.$el.querySelector('.flash-alert')).not.toBeNull();
- expect(vm.$el.querySelector('.flash-alert').textContent.trim()).toBe('error message');
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/index_spec.js b/spec/javascripts/repo/components/new_dropdown/index_spec.js
deleted file mode 100644
index 4a8e4445e2f..00000000000
--- a/spec/javascripts/repo/components/new_dropdown/index_spec.js
+++ /dev/null
@@ -1,77 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import newDropdown from '~/ide/components/new_dropdown/index.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('new dropdown component', () => {
- let vm;
-
- beforeEach(() => {
- const component = Vue.extend(newDropdown);
-
- vm = createComponentWithStore(component, store, {
- branch: 'master',
- path: '',
- });
-
- vm.$store.state.currentProjectId = 'abcproject';
- vm.$store.state.path = '';
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders new file, upload and new directory links', () => {
- expect(vm.$el.querySelectorAll('a')[0].textContent.trim()).toBe('New file');
- expect(vm.$el.querySelectorAll('a')[1].textContent.trim()).toBe('Upload file');
- expect(vm.$el.querySelectorAll('a')[2].textContent.trim()).toBe('New directory');
- });
-
- describe('createNewItem', () => {
- it('sets modalType to blob when new file is clicked', () => {
- vm.$el.querySelectorAll('a')[0].click();
-
- expect(vm.modalType).toBe('blob');
- });
-
- it('sets modalType to tree when new directory is clicked', () => {
- vm.$el.querySelectorAll('a')[2].click();
-
- expect(vm.modalType).toBe('tree');
- });
-
- it('opens modal when link is clicked', (done) => {
- vm.$el.querySelectorAll('a')[0].click();
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.modal')).not.toBeNull();
-
- done();
- });
- });
- });
-
- describe('hideModal', () => {
- beforeAll((done) => {
- vm.openModal = true;
- Vue.nextTick(done);
- });
-
- it('closes modal after toggling', (done) => {
- vm.hideModal();
-
- Vue.nextTick()
- .then(() => {
- expect(vm.$el.querySelector('.modal')).toBeNull();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/modal_spec.js b/spec/javascripts/repo/components/new_dropdown/modal_spec.js
deleted file mode 100644
index d6a1fdd115c..00000000000
--- a/spec/javascripts/repo/components/new_dropdown/modal_spec.js
+++ /dev/null
@@ -1,237 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import modal from '~/ide/components/new_dropdown/modal.vue';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { file, resetStore } from '../../helpers';
-
-describe('new file modal component', () => {
- const Component = Vue.extend(modal);
- let vm;
- let projectTree;
-
- beforeEach(() => {
- spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
- data: {
- id: '123',
- },
- }));
-
- spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- data: {
- commit: {
- id: '123branch',
- },
- },
- }));
-
- spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
- headers: {
- 'page-title': 'test',
- },
- json: () => Promise.resolve({
- last_commit_path: 'last_commit_path',
- parent_tree_url: 'parent_tree_url',
- path: '/',
- trees: [{ name: 'tree' }],
- blobs: [{ name: 'blob' }],
- submodules: [{ name: 'submodule' }],
- }),
- }));
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- ['tree', 'blob'].forEach((type) => {
- describe(type, () => {
- beforeEach(() => {
- store.state.projects.abcproject = {
- web_url: '',
- };
- store.state.trees = [];
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
- projectTree = store.state.trees['abcproject/mybranch'];
- store.state.currentProjectId = 'abcproject';
-
- vm = createComponentWithStore(Component, store, {
- type,
- branchId: 'master',
- path: '',
- parent: projectTree,
- });
-
- vm.entryName = 'testing';
-
- vm.$mount();
- });
-
- it(`sets modal title as ${type}`, () => {
- const title = type === 'tree' ? 'directory' : 'file';
-
- expect(vm.$el.querySelector('.modal-title').textContent.trim()).toBe(`Create new ${title}`);
- });
-
- it(`sets button label as ${type}`, () => {
- const title = type === 'tree' ? 'directory' : 'file';
-
- expect(vm.$el.querySelector('.btn-success').textContent.trim()).toBe(`Create ${title}`);
- });
-
- it(`sets form label as ${type}`, () => {
- const title = type === 'tree' ? 'Directory' : 'File';
-
- expect(vm.$el.querySelector('.label-light').textContent.trim()).toBe(`${title} name`);
- });
-
- describe('createEntryInStore', () => {
- it('calls createTempEntry', () => {
- spyOn(vm, 'createTempEntry');
-
- vm.createEntryInStore();
-
- expect(vm.createTempEntry).toHaveBeenCalledWith({
- projectId: 'abcproject',
- branchId: 'master',
- parent: projectTree,
- name: 'testing',
- type,
- });
- });
-
- it('sets editMode to true', (done) => {
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(vm.$store.state.editMode).toBeTruthy();
-
- done();
- });
- });
-
- it('toggles blob view', (done) => {
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(vm.$store.state.currentBlobView).toBe('repo-editor');
-
- done();
- });
- });
-
- it('opens newly created file', (done) => {
- if (type === 'blob') {
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(vm.$store.state.openFiles.length).toBe(1);
- expect(vm.$store.state.openFiles[0].name).toBe(type === 'blob' ? 'testing' : '.gitkeep');
-
- done();
- });
- } else {
- done();
- }
- });
-
- if (type === 'blob') {
- it('creates new file', (done) => {
- vm.createEntryInStore();
-
- setTimeout(() => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('testing');
- expect(baseTree[0].type).toBe('blob');
- expect(baseTree[0].tempFile).toBeTruthy();
-
- done();
- });
- });
-
- it('does not create temp file when file already exists', (done) => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- baseTree.push(file('testing', '1', type));
-
- vm.createEntryInStore();
-
- setTimeout(() => {
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('testing');
- expect(baseTree[0].type).toBe('blob');
- expect(baseTree[0].tempFile).toBeFalsy();
-
- done();
- });
- });
- } else {
- it('creates new tree', () => {
- vm.createEntryInStore();
-
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('testing');
- expect(baseTree[0].type).toBe('tree');
- expect(baseTree[0].tempFile).toBeTruthy();
- });
-
- it('creates multiple trees when entryName has slashes', () => {
- vm.entryName = 'app/test';
- vm.createEntryInStore();
-
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('app');
- });
-
- it('creates tree in existing tree', () => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- baseTree.push(file('app', '1', 'tree'));
-
- vm.entryName = 'app/test';
- vm.createEntryInStore();
-
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('app');
- expect(baseTree[0].tempFile).toBeFalsy();
- expect(baseTree[0].tree[0].tempFile).toBeTruthy();
- expect(baseTree[0].tree[0].name).toBe('test');
- });
-
- it('does not create new tree when already exists', () => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- baseTree.push(file('app', '1', 'tree'));
-
- vm.entryName = 'app';
- vm.createEntryInStore();
-
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe('app');
- expect(baseTree[0].tempFile).toBeFalsy();
- expect(baseTree[0].tree.length).toBe(0);
- });
- }
- });
- });
- });
-
- it('focuses field on mount', () => {
- document.body.innerHTML += '<div class="js-test"></div>';
-
- vm = createComponentWithStore(Component, store, {
- type: 'tree',
- projectId: 'abcproject',
- branchId: 'master',
- path: '',
- }).$mount('.js-test');
-
- expect(document.activeElement).toBe(vm.$refs.fieldName);
-
- vm.$el.remove();
- });
-});
diff --git a/spec/javascripts/repo/components/new_dropdown/upload_spec.js b/spec/javascripts/repo/components/new_dropdown/upload_spec.js
deleted file mode 100644
index ee8aab3a252..00000000000
--- a/spec/javascripts/repo/components/new_dropdown/upload_spec.js
+++ /dev/null
@@ -1,158 +0,0 @@
-import Vue from 'vue';
-import upload from '~/ide/components/new_dropdown/upload.vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { createComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { resetStore } from '../../helpers';
-
-describe('new dropdown upload', () => {
- let vm;
- let projectTree;
-
- beforeEach(() => {
- spyOn(service, 'getProjectData').and.returnValue(Promise.resolve({
- data: {
- id: '123',
- },
- }));
-
- spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- data: {
- commit: {
- id: '123branch',
- },
- },
- }));
-
- spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
- headers: {
- 'page-title': 'test',
- },
- json: () => Promise.resolve({
- last_commit_path: 'last_commit_path',
- parent_tree_url: 'parent_tree_url',
- path: '/',
- trees: [{ name: 'tree' }],
- blobs: [{ name: 'blob' }],
- submodules: [{ name: 'submodule' }],
- }),
- }));
-
- const Component = Vue.extend(upload);
-
- store.state.projects.abcproject = {
- web_url: '',
- };
- store.state.currentProjectId = 'abcproject';
- store.state.trees = [];
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
- projectTree = store.state.trees['abcproject/mybranch'];
-
- vm = createComponentWithStore(Component, store, {
- branchId: 'master',
- path: '',
- parent: projectTree,
- });
-
- vm.entryName = 'testing';
-
- vm.$mount();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- describe('readFile', () => {
- beforeEach(() => {
- spyOn(FileReader.prototype, 'readAsText');
- spyOn(FileReader.prototype, 'readAsDataURL');
- });
-
- it('calls readAsText for text files', () => {
- const file = {
- type: 'text/html',
- };
-
- vm.readFile(file);
-
- expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(file);
- });
-
- it('calls readAsDataURL for non-text files', () => {
- const file = {
- type: 'images/png',
- };
-
- vm.readFile(file);
-
- expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file);
- });
- });
-
- describe('createFile', () => {
- const target = {
- result: 'content',
- };
- const binaryTarget = {
- result: 'base64,base64content',
- };
- const file = {
- name: 'file',
- };
-
- it('creates new file', (done) => {
- vm.createFile(target, file, true);
-
- vm.$nextTick(() => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe(file.name);
- expect(baseTree[0].content).toBe(target.result);
-
- done();
- });
- });
-
- it('creates new file in path', (done) => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- const tree = {
- type: 'tree',
- name: 'testing',
- path: 'testing',
- tree: [],
- };
- baseTree.push(tree);
-
- vm.parent = tree;
- vm.createFile(target, file, true);
-
- vm.$nextTick(() => {
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].tree[0].name).toBe(file.name);
- expect(baseTree[0].tree[0].content).toBe(target.result);
- expect(baseTree[0].tree[0].path).toBe(`testing/${file.name}`);
-
- done();
- });
- });
-
- it('splits content on base64 if binary', (done) => {
- vm.createFile(binaryTarget, file, false);
-
- vm.$nextTick(() => {
- const baseTree = vm.$store.state.trees['abcproject/mybranch'].tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].name).toBe(file.name);
- expect(baseTree[0].content).toBe(binaryTarget.result.split('base64,')[1]);
- expect(baseTree[0].base64).toBe(true);
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_commit_section_spec.js b/spec/javascripts/repo/components/repo_commit_section_spec.js
deleted file mode 100644
index 934ada9dec2..00000000000
--- a/spec/javascripts/repo/components/repo_commit_section_spec.js
+++ /dev/null
@@ -1,140 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import repoCommitSection from '~/ide/components/repo_commit_section.vue';
-import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
-import { file, resetStore } from '../helpers';
-
-describe('RepoCommitSection', () => {
- let vm;
-
- function createComponent() {
- const RepoCommitSection = Vue.extend(repoCommitSection);
-
- const comp = new RepoCommitSection({
- store,
- }).$mount();
-
- comp.$store.state.currentProjectId = 'abcproject';
- comp.$store.state.currentBranchId = 'master';
- comp.$store.state.projects.abcproject = {
- web_url: '',
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
-
- comp.$store.state.rightPanelCollapsed = false;
- comp.$store.state.currentBranch = 'master';
- comp.$store.state.openFiles = [file('file1'), file('file2')];
- comp.$store.state.openFiles.forEach(f => Object.assign(f, {
- changed: true,
- content: 'testing',
- }));
-
- return comp.$mount();
- }
-
- beforeEach((done) => {
- vm = createComponent();
-
- spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
- headers: {
- 'page-title': 'test',
- },
- json: () => Promise.resolve({
- last_commit_path: 'last_commit_path',
- parent_tree_url: 'parent_tree_url',
- path: '/',
- trees: [{ name: 'tree' }],
- blobs: [{ name: 'blob' }],
- submodules: [{ name: 'submodule' }],
- }),
- }));
-
- Vue.nextTick(done);
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders a commit section', () => {
- const changedFileElements = [...vm.$el.querySelectorAll('.multi-file-commit-list li')];
- const submitCommit = vm.$el.querySelector('form .btn');
-
- expect(vm.$el.querySelector('.multi-file-commit-form')).not.toBeNull();
- expect(changedFileElements.length).toEqual(2);
-
- changedFileElements.forEach((changedFile, i) => {
- expect(changedFile.textContent.trim()).toEqual(vm.$store.getters.changedFiles[i].path);
- });
-
- expect(submitCommit.disabled).toBeTruthy();
- expect(submitCommit.querySelector('.fa-spinner.fa-spin')).toBeNull();
- });
-
- describe('when submitting', () => {
- let changedFiles;
-
- beforeEach(() => {
- vm.commitMessage = 'testing';
- changedFiles = JSON.parse(JSON.stringify(vm.$store.getters.changedFiles));
-
- spyOn(service, 'commit').and.returnValue(Promise.resolve({
- data: {
- short_id: '1',
- stats: {},
- },
- }));
- });
-
- it('allows you to submit', () => {
- expect(vm.$el.querySelector('form .btn').disabled).toBeTruthy();
- });
-
- it('submits commit', (done) => {
- vm.makeCommit();
-
- // Wait for the branch check to finish
- getSetTimeoutPromise()
- .then(() => Vue.nextTick())
- .then(() => {
- const args = service.commit.calls.allArgs()[0];
- const { commit_message, actions, branch: payloadBranch } = args[1];
-
- expect(commit_message).toBe('testing');
- expect(actions.length).toEqual(2);
- expect(payloadBranch).toEqual('master');
- expect(actions[0].action).toEqual('update');
- expect(actions[1].action).toEqual('update');
- expect(actions[0].content).toEqual(changedFiles[0].content);
- expect(actions[1].content).toEqual(changedFiles[1].content);
- expect(actions[0].file_path).toEqual(changedFiles[0].path);
- expect(actions[1].file_path).toEqual(changedFiles[1].path);
- })
- .then(done)
- .catch(done.fail);
- });
-
- it('redirects to MR creation page if start new MR checkbox checked', (done) => {
- spyOn(urlUtils, 'visitUrl');
- vm.startNewMR = true;
-
- vm.makeCommit();
-
- getSetTimeoutPromise()
- .then(() => Vue.nextTick())
- .then(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalled();
- })
- .then(done)
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_edit_button_spec.js b/spec/javascripts/repo/components/repo_edit_button_spec.js
deleted file mode 100644
index 2895b794506..00000000000
--- a/spec/javascripts/repo/components/repo_edit_button_spec.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoEditButton from '~/ide/components/repo_edit_button.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoEditButton', () => {
- let vm;
-
- beforeEach(() => {
- const f = file();
- const RepoEditButton = Vue.extend(repoEditButton);
-
- vm = new RepoEditButton({
- store,
- });
-
- f.active = true;
- vm.$store.dispatch('setInitialData', {
- canCommit: true,
- onTopOfBranch: true,
- });
- vm.$store.state.openFiles.push(f);
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders an edit button', () => {
- vm.$mount();
-
- expect(vm.$el.querySelector('.btn')).not.toBeNull();
- expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
- });
-
- it('renders edit button with cancel text', () => {
- vm.$store.state.editMode = true;
-
- vm.$mount();
-
- expect(vm.$el.querySelector('.btn')).not.toBeNull();
- expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Cancel edit');
- });
-
- it('toggles edit mode on click', (done) => {
- vm.$mount();
-
- vm.$el.querySelector('.btn').click();
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.btn').textContent.trim()).toBe('Edit');
-
- done();
- });
- });
-
- describe('discardPopupOpen', () => {
- beforeEach(() => {
- vm.$store.state.discardPopupOpen = true;
- vm.$store.state.editMode = true;
- vm.$store.state.openFiles[0].changed = true;
-
- vm.$mount();
- });
-
- it('renders popup', () => {
- expect(vm.$el.querySelector('.modal')).not.toBeNull();
- });
-
- it('removes all changed files', (done) => {
- vm.$el.querySelector('.btn-warning').click();
-
- vm.$nextTick(() => {
- expect(vm.$store.getters.changedFiles.length).toBe(0);
- expect(vm.$el.querySelector('.modal')).toBeNull();
-
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_editor_spec.js b/spec/javascripts/repo/components/repo_editor_spec.js
deleted file mode 100644
index e7b2ed08acd..00000000000
--- a/spec/javascripts/repo/components/repo_editor_spec.js
+++ /dev/null
@@ -1,60 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoEditor from '~/ide/components/repo_editor.vue';
-import monacoLoader from '~/ide/monaco_loader';
-import { file, resetStore } from '../helpers';
-
-describe('RepoEditor', () => {
- let vm;
-
- beforeEach((done) => {
- const f = file();
- const RepoEditor = Vue.extend(repoEditor);
-
- vm = new RepoEditor({
- store,
- });
-
- f.active = true;
- f.tempFile = true;
- vm.$store.state.openFiles.push(f);
- vm.$store.getters.activeFile.html = 'testing';
- vm.monaco = true;
-
- vm.$mount();
-
- monacoLoader(['vs/editor/editor.main'], () => {
- setTimeout(done, 0);
- });
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders an ide container', (done) => {
- Vue.nextTick(() => {
- expect(vm.shouldHideEditor).toBeFalsy();
-
- done();
- });
- });
-
- describe('when open file is binary and not raw', () => {
- beforeEach((done) => {
- vm.$store.getters.activeFile.binary = true;
-
- Vue.nextTick(done);
- });
-
- it('does not render the IDE', () => {
- expect(vm.shouldHideEditor).toBeTruthy();
- });
-
- it('shows activeFile html', () => {
- expect(vm.$el.textContent).toContain('testing');
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_file_buttons_spec.js b/spec/javascripts/repo/components/repo_file_buttons_spec.js
deleted file mode 100644
index 115569a9117..00000000000
--- a/spec/javascripts/repo/components/repo_file_buttons_spec.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoFileButtons from '~/ide/components/repo_file_buttons.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoFileButtons', () => {
- const activeFile = file();
- let vm;
-
- function createComponent() {
- const RepoFileButtons = Vue.extend(repoFileButtons);
-
- activeFile.rawPath = 'test';
- activeFile.blamePath = 'test';
- activeFile.commitsPath = 'test';
- activeFile.active = true;
- store.state.openFiles.push(activeFile);
-
- return new RepoFileButtons({
- store,
- }).$mount();
- }
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders Raw, Blame, History, Permalink and Preview toggle', (done) => {
- vm = createComponent();
-
- vm.$nextTick(() => {
- const raw = vm.$el.querySelector('.raw');
- const blame = vm.$el.querySelector('.blame');
- const history = vm.$el.querySelector('.history');
-
- expect(raw.href).toMatch(`/${activeFile.rawPath}`);
- expect(raw.textContent.trim()).toEqual('Raw');
- expect(blame.href).toMatch(`/${activeFile.blamePath}`);
- expect(blame.textContent.trim()).toEqual('Blame');
- expect(history.href).toMatch(`/${activeFile.commitsPath}`);
- expect(history.textContent.trim()).toEqual('History');
- expect(vm.$el.querySelector('.permalink').textContent.trim()).toEqual('Permalink');
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js
deleted file mode 100644
index 27b55ed1f87..00000000000
--- a/spec/javascripts/repo/components/repo_file_spec.js
+++ /dev/null
@@ -1,98 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoFile from '~/ide/components/repo_file.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoFile', () => {
- const updated = 'updated';
- let vm;
-
- function createComponent(propsData) {
- const RepoFile = Vue.extend(repoFile);
-
- return new RepoFile({
- store,
- propsData,
- }).$mount();
- }
-
- afterEach(() => {
- resetStore(vm.$store);
- });
-
- it('renders link, icon and name', () => {
- const RepoFile = Vue.extend(repoFile);
- vm = new RepoFile({
- store,
- propsData: {
- file: file('t4'),
- },
- });
- spyOn(vm, 'timeFormated').and.returnValue(updated);
- vm.$mount();
-
- const name = vm.$el.querySelector('.repo-file-name');
-
- expect(name.href).toMatch('');
- expect(name.textContent.trim()).toEqual(vm.file.name);
- });
-
- it('does render if hasFiles is true and is loading tree', () => {
- vm = createComponent({
- file: file('t1'),
- });
-
- expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy();
- });
-
- it('does not render commit message and datetime if mini', (done) => {
- vm = createComponent({
- file: file('t2'),
- });
- vm.$store.state.openFiles.push(vm.file);
-
- vm.$nextTick(() => {
- expect(vm.$el.querySelector('.commit-message')).toBeFalsy();
- expect(vm.$el.querySelector('.commit-update')).toBeFalsy();
-
- done();
- });
- });
-
- it('fires clickFile when the link is clicked', () => {
- vm = createComponent({
- file: file('t3'),
- });
-
- spyOn(vm, 'clickFile');
-
- vm.$el.click();
-
- expect(vm.clickFile).toHaveBeenCalledWith(vm.file);
- });
-
- describe('submodule', () => {
- let f;
-
- beforeEach(() => {
- f = file('submodule name', '123456789');
- f.type = 'submodule';
-
- vm = createComponent({
- file: f,
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('renders submodule short ID', () => {
- expect(vm.$el.querySelector('.commit-sha').textContent.trim()).toBe('12345678');
- });
-
- it('renders ID next to submodule name', () => {
- expect(vm.$el.querySelector('td').textContent.replace(/\s+/g, ' ')).toContain('submodule name @ 12345678');
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_loading_file_spec.js b/spec/javascripts/repo/components/repo_loading_file_spec.js
deleted file mode 100644
index 18366fb89bc..00000000000
--- a/spec/javascripts/repo/components/repo_loading_file_spec.js
+++ /dev/null
@@ -1,63 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoLoadingFile from '~/ide/components/repo_loading_file.vue';
-import { resetStore } from '../helpers';
-
-describe('RepoLoadingFile', () => {
- let vm;
-
- function createComponent() {
- const RepoLoadingFile = Vue.extend(repoLoadingFile);
-
- return new RepoLoadingFile({
- store,
- }).$mount();
- }
-
- function assertLines(lines) {
- lines.forEach((line, n) => {
- const index = n + 1;
- expect(line.classList.contains(`skeleton-line-${index}`)).toBeTruthy();
- });
- }
-
- function assertColumns(columns) {
- columns.forEach((column) => {
- const container = column.querySelector('.animation-container');
- const lines = [...container.querySelectorAll(':scope > div')];
-
- expect(container).toBeTruthy();
- expect(lines.length).toEqual(6);
- assertLines(lines);
- });
- }
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders 3 columns of animated LoC', () => {
- vm = createComponent();
- const columns = [...vm.$el.querySelectorAll('td')];
-
- expect(columns.length).toEqual(3);
- assertColumns(columns);
- });
-
- it('renders 1 column of animated LoC if isMini', (done) => {
- vm = createComponent();
- vm.$store.state.leftPanelCollapsed = true;
- vm.$store.state.openFiles.push('test');
-
- vm.$nextTick(() => {
- const columns = [...vm.$el.querySelectorAll('td')];
-
- expect(columns.length).toEqual(1);
- assertColumns(columns);
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_prev_directory_spec.js b/spec/javascripts/repo/components/repo_prev_directory_spec.js
deleted file mode 100644
index ff26cab2262..00000000000
--- a/spec/javascripts/repo/components/repo_prev_directory_spec.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoPrevDirectory from '~/ide/components/repo_prev_directory.vue';
-import { resetStore } from '../helpers';
-
-describe('RepoPrevDirectory', () => {
- let vm;
- const parentLink = 'parent';
- function createComponent() {
- const RepoPrevDirectory = Vue.extend(repoPrevDirectory);
-
- const comp = new RepoPrevDirectory({
- store,
- });
-
- comp.$store.state.parentTreeUrl = parentLink;
-
- return comp.$mount();
- }
-
- beforeEach(() => {
- vm = createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders a prev dir link', () => {
- const link = vm.$el.querySelector('a');
-
- expect(link.href).toMatch(`/${parentLink}`);
- expect(link.textContent).toEqual('...');
- });
-
- it('clicking row triggers getTreeData', () => {
- spyOn(vm, 'getTreeData');
-
- vm.$el.querySelector('td').click();
-
- expect(vm.getTreeData).toHaveBeenCalledWith({ endpoint: parentLink });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_preview_spec.js b/spec/javascripts/repo/components/repo_preview_spec.js
deleted file mode 100644
index e90837e4cb2..00000000000
--- a/spec/javascripts/repo/components/repo_preview_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoPreview from '~/ide/components/repo_preview.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoPreview', () => {
- let vm;
-
- function createComponent() {
- const f = file();
- const RepoPreview = Vue.extend(repoPreview);
-
- const comp = new RepoPreview({
- store,
- });
-
- f.active = true;
- f.html = 'test';
-
- comp.$store.state.openFiles.push(f);
-
- return comp.$mount();
- }
-
- afterEach(() => {
- vm.$destroy();
-
- resetStore(vm.$store);
- });
-
- it('renders a div with the activeFile html', () => {
- vm = createComponent();
-
- expect(vm.$el.tagName).toEqual('DIV');
- expect(vm.$el.innerHTML).toContain('test');
- });
-});
diff --git a/spec/javascripts/repo/components/repo_tab_spec.js b/spec/javascripts/repo/components/repo_tab_spec.js
deleted file mode 100644
index 933e8d3a06a..00000000000
--- a/spec/javascripts/repo/components/repo_tab_spec.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoTab from '~/ide/components/repo_tab.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoTab', () => {
- let vm;
-
- function createComponent(propsData) {
- const RepoTab = Vue.extend(repoTab);
-
- return new RepoTab({
- store,
- propsData,
- }).$mount();
- }
-
- afterEach(() => {
- resetStore(vm.$store);
- });
-
- it('renders a close link and a name link', () => {
- vm = createComponent({
- tab: file(),
- });
- vm.$store.state.openFiles.push(vm.tab);
- const close = vm.$el.querySelector('.multi-file-tab-close');
- const name = vm.$el.querySelector(`[title="${vm.tab.url}"]`);
-
- expect(close.querySelector('.fa-times')).toBeTruthy();
- expect(name.textContent.trim()).toEqual(vm.tab.name);
- });
-
- it('fires clickFile when the link is clicked', () => {
- vm = createComponent({
- tab: file(),
- });
-
- spyOn(vm, 'clickFile');
-
- vm.$el.click();
-
- expect(vm.clickFile).toHaveBeenCalledWith(vm.tab);
- });
-
- it('calls closeFile when clicking close button', () => {
- vm = createComponent({
- tab: file(),
- });
-
- spyOn(vm, 'closeFile');
-
- vm.$el.querySelector('.multi-file-tab-close').click();
-
- expect(vm.closeFile).toHaveBeenCalledWith({ file: vm.tab });
- });
-
- it('renders an fa-circle icon if tab is changed', () => {
- const tab = file('changedFile');
- tab.changed = true;
- vm = createComponent({
- tab,
- });
-
- expect(vm.$el.querySelector('.multi-file-tab-close .fa-circle')).not.toBeNull();
- });
-
- describe('methods', () => {
- describe('closeTab', () => {
- it('does not close tab if is changed', (done) => {
- const tab = file('closeFile');
- tab.changed = true;
- tab.opened = true;
- vm = createComponent({
- tab,
- });
- vm.$store.state.openFiles.push(tab);
- vm.$store.dispatch('setFileActive', tab);
-
- vm.$el.querySelector('.multi-file-tab-close').click();
-
- vm.$nextTick(() => {
- expect(tab.opened).toBeTruthy();
-
- done();
- });
- });
-
- it('closes tab when clicking close btn', (done) => {
- const tab = file('lose');
- tab.opened = true;
- vm = createComponent({
- tab,
- });
- vm.$store.state.openFiles.push(tab);
- vm.$store.dispatch('setFileActive', tab);
-
- vm.$el.querySelector('.multi-file-tab-close').click();
-
- vm.$nextTick(() => {
- expect(tab.opened).toBeFalsy();
-
- done();
- });
- });
- });
- });
-});
diff --git a/spec/javascripts/repo/components/repo_tabs_spec.js b/spec/javascripts/repo/components/repo_tabs_spec.js
deleted file mode 100644
index 2c363364d70..00000000000
--- a/spec/javascripts/repo/components/repo_tabs_spec.js
+++ /dev/null
@@ -1,37 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import repoTabs from '~/ide/components/repo_tabs.vue';
-import { file, resetStore } from '../helpers';
-
-describe('RepoTabs', () => {
- const openedFiles = [file('open1'), file('open2')];
- let vm;
-
- function createComponent() {
- const RepoTabs = Vue.extend(repoTabs);
-
- return new RepoTabs({
- store,
- }).$mount();
- }
-
- afterEach(() => {
- resetStore(vm.$store);
- });
-
- it('renders a list of tabs', (done) => {
- vm = createComponent();
- openedFiles[0].active = true;
- vm.$store.state.openFiles = openedFiles;
-
- vm.$nextTick(() => {
- const tabs = [...vm.$el.querySelectorAll('.multi-file-tab')];
-
- expect(tabs.length).toEqual(2);
- expect(tabs[0].classList.contains('active')).toBeTruthy();
- expect(tabs[1].classList.contains('active')).toBeFalsy();
-
- done();
- });
- });
-});
diff --git a/spec/javascripts/repo/helpers.js b/spec/javascripts/repo/helpers.js
deleted file mode 100644
index ac43d221198..00000000000
--- a/spec/javascripts/repo/helpers.js
+++ /dev/null
@@ -1,16 +0,0 @@
-import { decorateData } from '~/ide/stores/utils';
-import state from '~/ide/stores/state';
-
-export const resetStore = (store) => {
- store.replaceState(state());
-};
-
-export const file = (name = 'name', id = name, type = '') => decorateData({
- id,
- type,
- icon: 'icon',
- url: 'url',
- name,
- path: name,
- lastCommit: {},
-});
diff --git a/spec/javascripts/repo/lib/common/disposable_spec.js b/spec/javascripts/repo/lib/common/disposable_spec.js
deleted file mode 100644
index af12ca15369..00000000000
--- a/spec/javascripts/repo/lib/common/disposable_spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import Disposable from '~/ide/lib/common/disposable';
-
-describe('Multi-file editor library disposable class', () => {
- let instance;
- let disposableClass;
-
- beforeEach(() => {
- instance = new Disposable();
-
- disposableClass = {
- dispose: jasmine.createSpy('dispose'),
- };
- });
-
- afterEach(() => {
- instance.dispose();
- });
-
- describe('add', () => {
- it('adds disposable classes', () => {
- instance.add(disposableClass);
-
- expect(instance.disposers.size).toBe(1);
- });
- });
-
- describe('dispose', () => {
- beforeEach(() => {
- instance.add(disposableClass);
- });
-
- it('calls dispose on all cached disposers', () => {
- instance.dispose();
-
- expect(disposableClass.dispose).toHaveBeenCalled();
- });
-
- it('clears cached disposers', () => {
- instance.dispose();
-
- expect(instance.disposers.size).toBe(0);
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/common/model_manager_spec.js b/spec/javascripts/repo/lib/common/model_manager_spec.js
deleted file mode 100644
index 563c2e33834..00000000000
--- a/spec/javascripts/repo/lib/common/model_manager_spec.js
+++ /dev/null
@@ -1,81 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import ModelManager from '~/ide/lib/common/model_manager';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library model manager', () => {
- let instance;
-
- beforeEach((done) => {
- monacoLoader(['vs/editor/editor.main'], () => {
- instance = new ModelManager(monaco);
-
- done();
- });
- });
-
- afterEach(() => {
- instance.dispose();
- });
-
- describe('addModel', () => {
- it('caches model', () => {
- instance.addModel(file());
-
- expect(instance.models.size).toBe(1);
- });
-
- it('caches model by file path', () => {
- instance.addModel(file('path-name'));
-
- expect(instance.models.keys().next().value).toBe('path-name');
- });
-
- it('adds model into disposable', () => {
- spyOn(instance.disposable, 'add').and.callThrough();
-
- instance.addModel(file());
-
- expect(instance.disposable.add).toHaveBeenCalled();
- });
-
- it('returns cached model', () => {
- spyOn(instance.models, 'get').and.callThrough();
-
- instance.addModel(file());
- instance.addModel(file());
-
- expect(instance.models.get).toHaveBeenCalled();
- });
- });
-
- describe('hasCachedModel', () => {
- it('returns false when no models exist', () => {
- expect(instance.hasCachedModel('path')).toBeFalsy();
- });
-
- it('returns true when model exists', () => {
- instance.addModel(file('path-name'));
-
- expect(instance.hasCachedModel('path-name')).toBeTruthy();
- });
- });
-
- describe('dispose', () => {
- it('clears cached models', () => {
- instance.addModel(file());
-
- instance.dispose();
-
- expect(instance.models.size).toBe(0);
- });
-
- it('calls disposable dispose', () => {
- spyOn(instance.disposable, 'dispose').and.callThrough();
-
- instance.dispose();
-
- expect(instance.disposable.dispose).toHaveBeenCalled();
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/common/model_spec.js b/spec/javascripts/repo/lib/common/model_spec.js
deleted file mode 100644
index 878a4a3f3fe..00000000000
--- a/spec/javascripts/repo/lib/common/model_spec.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import Model from '~/ide/lib/common/model';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library model', () => {
- let model;
-
- beforeEach((done) => {
- monacoLoader(['vs/editor/editor.main'], () => {
- model = new Model(monaco, file('path'));
-
- done();
- });
- });
-
- afterEach(() => {
- model.dispose();
- });
-
- it('creates original model & new model', () => {
- expect(model.originalModel).not.toBeNull();
- expect(model.model).not.toBeNull();
- });
-
- describe('path', () => {
- it('returns file path', () => {
- expect(model.path).toBe('path');
- });
- });
-
- describe('getModel', () => {
- it('returns model', () => {
- expect(model.getModel()).toBe(model.model);
- });
- });
-
- describe('getOriginalModel', () => {
- it('returns original model', () => {
- expect(model.getOriginalModel()).toBe(model.originalModel);
- });
- });
-
- describe('onChange', () => {
- it('caches event by path', () => {
- model.onChange(() => {});
-
- expect(model.events.size).toBe(1);
- expect(model.events.keys().next().value).toBe('path');
- });
-
- it('calls callback on change', (done) => {
- const spy = jasmine.createSpy();
- model.onChange(spy);
-
- model.getModel().setValue('123');
-
- setTimeout(() => {
- expect(spy).toHaveBeenCalledWith(model.getModel(), jasmine.anything());
- done();
- });
- });
- });
-
- describe('dispose', () => {
- it('calls disposable dispose', () => {
- spyOn(model.disposable, 'dispose').and.callThrough();
-
- model.dispose();
-
- expect(model.disposable.dispose).toHaveBeenCalled();
- });
-
- it('clears events', () => {
- model.onChange(() => {});
-
- expect(model.events.size).toBe(1);
-
- model.dispose();
-
- expect(model.events.size).toBe(0);
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/decorations/controller_spec.js b/spec/javascripts/repo/lib/decorations/controller_spec.js
deleted file mode 100644
index fea12d74dca..00000000000
--- a/spec/javascripts/repo/lib/decorations/controller_spec.js
+++ /dev/null
@@ -1,120 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import DecorationsController from '~/ide/lib/decorations/controller';
-import Model from '~/ide/lib/common/model';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library decorations controller', () => {
- let editorInstance;
- let controller;
- let model;
-
- beforeEach((done) => {
- monacoLoader(['vs/editor/editor.main'], () => {
- editorInstance = editor.create(monaco);
- editorInstance.createInstance(document.createElement('div'));
-
- controller = new DecorationsController(editorInstance);
- model = new Model(monaco, file('path'));
-
- done();
- });
- });
-
- afterEach(() => {
- model.dispose();
- editorInstance.dispose();
- controller.dispose();
- });
-
- describe('getAllDecorationsForModel', () => {
- it('returns empty array when no decorations exist for model', () => {
- const decorations = controller.getAllDecorationsForModel(model);
-
- expect(decorations).toEqual([]);
- });
-
- it('returns decorations by model URL', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- const decorations = controller.getAllDecorationsForModel(model);
-
- expect(decorations[0]).toEqual({ decoration: 'decorationValue' });
- });
- });
-
- describe('addDecorations', () => {
- it('caches decorations in a new map', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- expect(controller.decorations.size).toBe(1);
- });
-
- it('does not create new cache model', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue2' }]);
-
- expect(controller.decorations.size).toBe(1);
- });
-
- it('caches decorations by model URL', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- expect(controller.decorations.size).toBe(1);
- expect(controller.decorations.keys().next().value).toBe('path');
- });
-
- it('calls decorate method', () => {
- spyOn(controller, 'decorate');
-
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- expect(controller.decorate).toHaveBeenCalled();
- });
- });
-
- describe('decorate', () => {
- it('sets decorations on editor instance', () => {
- spyOn(controller.editor.instance, 'deltaDecorations');
-
- controller.decorate(model);
-
- expect(controller.editor.instance.deltaDecorations).toHaveBeenCalledWith([], []);
- });
-
- it('caches decorations', () => {
- spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
-
- controller.decorate(model);
-
- expect(controller.editorDecorations.size).toBe(1);
- });
-
- it('caches decorations by model URL', () => {
- spyOn(controller.editor.instance, 'deltaDecorations').and.returnValue([]);
-
- controller.decorate(model);
-
- expect(controller.editorDecorations.keys().next().value).toBe('path');
- });
- });
-
- describe('dispose', () => {
- it('clears cached decorations', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- controller.dispose();
-
- expect(controller.decorations.size).toBe(0);
- });
-
- it('clears cached editorDecorations', () => {
- controller.addDecorations(model, 'key', [{ decoration: 'decorationValue' }]);
-
- controller.dispose();
-
- expect(controller.editorDecorations.size).toBe(0);
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/diff/controller_spec.js b/spec/javascripts/repo/lib/diff/controller_spec.js
deleted file mode 100644
index 1d55c165260..00000000000
--- a/spec/javascripts/repo/lib/diff/controller_spec.js
+++ /dev/null
@@ -1,176 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import ModelManager from '~/ide/lib/common/model_manager';
-import DecorationsController from '~/ide/lib/decorations/controller';
-import DirtyDiffController, { getDiffChangeType, getDecorator } from '~/ide/lib/diff/controller';
-import { computeDiff } from '~/ide/lib/diff/diff';
-import { file } from '../../helpers';
-
-describe('Multi-file editor library dirty diff controller', () => {
- let editorInstance;
- let controller;
- let modelManager;
- let decorationsController;
- let model;
-
- beforeEach((done) => {
- monacoLoader(['vs/editor/editor.main'], () => {
- editorInstance = editor.create(monaco);
- editorInstance.createInstance(document.createElement('div'));
-
- modelManager = new ModelManager(monaco);
- decorationsController = new DecorationsController(editorInstance);
-
- model = modelManager.addModel(file());
-
- controller = new DirtyDiffController(modelManager, decorationsController);
-
- done();
- });
- });
-
- afterEach(() => {
- controller.dispose();
- model.dispose();
- decorationsController.dispose();
- editorInstance.dispose();
- });
-
- describe('getDiffChangeType', () => {
- ['added', 'removed', 'modified'].forEach((type) => {
- it(`returns ${type}`, () => {
- const change = {
- [type]: true,
- };
-
- expect(getDiffChangeType(change)).toBe(type);
- });
- });
- });
-
- describe('getDecorator', () => {
- ['added', 'removed', 'modified'].forEach((type) => {
- it(`returns with linesDecorationsClassName for ${type}`, () => {
- const change = {
- [type]: true,
- };
-
- expect(
- getDecorator(change).options.linesDecorationsClassName,
- ).toBe(`dirty-diff dirty-diff-${type}`);
- });
-
- it('returns with line numbers', () => {
- const change = {
- lineNumber: 1,
- endLineNumber: 2,
- [type]: true,
- };
-
- const range = getDecorator(change).range;
-
- expect(range.startLineNumber).toBe(1);
- expect(range.endLineNumber).toBe(2);
- expect(range.startColumn).toBe(1);
- expect(range.endColumn).toBe(1);
- });
- });
- });
-
- describe('attachModel', () => {
- it('adds change event callback', () => {
- spyOn(model, 'onChange');
-
- controller.attachModel(model);
-
- expect(model.onChange).toHaveBeenCalled();
- });
-
- it('calls throttledComputeDiff on change', () => {
- spyOn(controller, 'throttledComputeDiff');
-
- controller.attachModel(model);
-
- model.getModel().setValue('123');
-
- expect(controller.throttledComputeDiff).toHaveBeenCalled();
- });
- });
-
- describe('computeDiff', () => {
- it('posts to worker', () => {
- spyOn(controller.dirtyDiffWorker, 'postMessage');
-
- controller.computeDiff(model);
-
- expect(controller.dirtyDiffWorker.postMessage).toHaveBeenCalledWith({
- path: model.path,
- originalContent: '',
- newContent: '',
- });
- });
- });
-
- describe('reDecorate', () => {
- it('calls decorations controller decorate', () => {
- spyOn(controller.decorationsController, 'decorate');
-
- controller.reDecorate(model);
-
- expect(controller.decorationsController.decorate).toHaveBeenCalledWith(model);
- });
- });
-
- describe('decorate', () => {
- it('adds decorations into decorations controller', () => {
- spyOn(controller.decorationsController, 'addDecorations');
-
- controller.decorate({ data: { changes: [], path: 'path' } });
-
- expect(controller.decorationsController.addDecorations).toHaveBeenCalledWith('path', 'dirtyDiff', jasmine.anything());
- });
-
- it('adds decorations into editor', () => {
- const spy = spyOn(controller.decorationsController.editor.instance, 'deltaDecorations');
-
- controller.decorate({ data: { changes: computeDiff('123', '1234'), path: 'path' } });
-
- expect(spy).toHaveBeenCalledWith([], [{
- range: new monaco.Range(
- 1, 1, 1, 1,
- ),
- options: {
- isWholeLine: true,
- linesDecorationsClassName: 'dirty-diff dirty-diff-modified',
- },
- }]);
- });
- });
-
- describe('dispose', () => {
- it('calls disposable dispose', () => {
- spyOn(controller.disposable, 'dispose').and.callThrough();
-
- controller.dispose();
-
- expect(controller.disposable.dispose).toHaveBeenCalled();
- });
-
- it('terminates worker', () => {
- spyOn(controller.dirtyDiffWorker, 'terminate').and.callThrough();
-
- controller.dispose();
-
- expect(controller.dirtyDiffWorker.terminate).toHaveBeenCalled();
- });
-
- it('removes worker event listener', () => {
- spyOn(controller.dirtyDiffWorker, 'removeEventListener').and.callThrough();
-
- controller.dispose();
-
- expect(controller.dirtyDiffWorker.removeEventListener).toHaveBeenCalledWith('message', jasmine.anything());
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/diff/diff_spec.js b/spec/javascripts/repo/lib/diff/diff_spec.js
deleted file mode 100644
index 57f3ac3d365..00000000000
--- a/spec/javascripts/repo/lib/diff/diff_spec.js
+++ /dev/null
@@ -1,80 +0,0 @@
-import { computeDiff } from '~/ide/lib/diff/diff';
-
-describe('Multi-file editor library diff calculator', () => {
- describe('computeDiff', () => {
- it('returns empty array if no changes', () => {
- const diff = computeDiff('123', '123');
-
- expect(diff).toEqual([]);
- });
-
- describe('modified', () => {
- it('', () => {
- const diff = computeDiff('123', '1234')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeUndefined();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n1234\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeUndefined();
- expect(diff.lineNumber).toBe(2);
- });
- });
-
- describe('added', () => {
- it('', () => {
- const diff = computeDiff('123', '123\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeUndefined();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n123\n1234\n123')[0];
-
- expect(diff.added).toBeTruthy();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeUndefined();
- expect(diff.lineNumber).toBe(3);
- });
- });
-
- describe('removed', () => {
- it('', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.added).toBeUndefined();
- expect(diff.modified).toBeUndefined();
- expect(diff.removed).toBeTruthy();
- });
-
- it('', () => {
- const diff = computeDiff('123\n123\n123', '123\n123')[0];
-
- expect(diff.added).toBeUndefined();
- expect(diff.modified).toBeTruthy();
- expect(diff.removed).toBeTruthy();
- expect(diff.lineNumber).toBe(2);
- });
- });
-
- it('includes line number of change', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.lineNumber).toBe(1);
- });
-
- it('includes end line number of change', () => {
- const diff = computeDiff('123', '')[0];
-
- expect(diff.endLineNumber).toBe(1);
- });
- });
-});
diff --git a/spec/javascripts/repo/lib/editor_options_spec.js b/spec/javascripts/repo/lib/editor_options_spec.js
deleted file mode 100644
index edbf5450dce..00000000000
--- a/spec/javascripts/repo/lib/editor_options_spec.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import editorOptions from '~/ide/lib/editor_options';
-
-describe('Multi-file editor library editor options', () => {
- it('returns an array', () => {
- expect(editorOptions).toEqual(jasmine.any(Array));
- });
-});
diff --git a/spec/javascripts/repo/lib/editor_spec.js b/spec/javascripts/repo/lib/editor_spec.js
deleted file mode 100644
index 8d51d48a782..00000000000
--- a/spec/javascripts/repo/lib/editor_spec.js
+++ /dev/null
@@ -1,128 +0,0 @@
-/* global monaco */
-import monacoLoader from '~/ide/monaco_loader';
-import editor from '~/ide/lib/editor';
-import { file } from '../helpers';
-
-describe('Multi-file editor library', () => {
- let instance;
-
- beforeEach((done) => {
- monacoLoader(['vs/editor/editor.main'], () => {
- instance = editor.create(monaco);
-
- done();
- });
- });
-
- afterEach(() => {
- instance.dispose();
- });
-
- it('creates instance of editor', () => {
- expect(editor.editorInstance).not.toBeNull();
- });
-
- describe('createInstance', () => {
- let el;
-
- beforeEach(() => {
- el = document.createElement('div');
- });
-
- it('creates editor instance', () => {
- spyOn(instance.monaco.editor, 'create').and.callThrough();
-
- instance.createInstance(el);
-
- expect(instance.monaco.editor.create).toHaveBeenCalled();
- });
-
- it('creates dirty diff controller', () => {
- instance.createInstance(el);
-
- expect(instance.dirtyDiffController).not.toBeNull();
- });
- });
-
- describe('createModel', () => {
- it('calls model manager addModel', () => {
- spyOn(instance.modelManager, 'addModel');
-
- instance.createModel('FILE');
-
- expect(instance.modelManager.addModel).toHaveBeenCalledWith('FILE');
- });
- });
-
- describe('attachModel', () => {
- let model;
-
- beforeEach(() => {
- instance.createInstance(document.createElement('div'));
-
- model = instance.createModel(file());
- });
-
- it('sets the current model on the instance', () => {
- instance.attachModel(model);
-
- expect(instance.currentModel).toBe(model);
- });
-
- it('attaches the model to the current instance', () => {
- spyOn(instance.instance, 'setModel');
-
- instance.attachModel(model);
-
- expect(instance.instance.setModel).toHaveBeenCalledWith(model.getModel());
- });
-
- it('attaches the model to the dirty diff controller', () => {
- spyOn(instance.dirtyDiffController, 'attachModel');
-
- instance.attachModel(model);
-
- expect(instance.dirtyDiffController.attachModel).toHaveBeenCalledWith(model);
- });
-
- it('re-decorates with the dirty diff controller', () => {
- spyOn(instance.dirtyDiffController, 'reDecorate');
-
- instance.attachModel(model);
-
- expect(instance.dirtyDiffController.reDecorate).toHaveBeenCalledWith(model);
- });
- });
-
- describe('clearEditor', () => {
- it('resets the editor model', () => {
- instance.createInstance(document.createElement('div'));
-
- spyOn(instance.instance, 'setModel');
-
- instance.clearEditor();
-
- expect(instance.instance.setModel).toHaveBeenCalledWith(null);
- });
- });
-
- describe('dispose', () => {
- it('calls disposble dispose method', () => {
- spyOn(instance.disposable, 'dispose').and.callThrough();
-
- instance.dispose();
-
- expect(instance.disposable.dispose).toHaveBeenCalled();
- });
-
- it('resets instance', () => {
- instance.createInstance(document.createElement('div'));
-
- expect(instance.instance).not.toBeNull();
-
- instance.dispose();
-
- expect(instance.instance).toBeNull();
- });
- });
-});
diff --git a/spec/javascripts/repo/monaco_loader_spec.js b/spec/javascripts/repo/monaco_loader_spec.js
deleted file mode 100644
index b8ac36972aa..00000000000
--- a/spec/javascripts/repo/monaco_loader_spec.js
+++ /dev/null
@@ -1,13 +0,0 @@
-import monacoContext from 'monaco-editor/dev/vs/loader';
-import monacoLoader from '~/ide/monaco_loader';
-
-describe('MonacoLoader', () => {
- it('calls require.config and exports require', () => {
- expect(monacoContext.require.getConfig()).toEqual(jasmine.objectContaining({
- paths: {
- vs: `${__webpack_public_path__}monaco-editor/vs`, // eslint-disable-line camelcase
- },
- }));
- expect(monacoLoader).toBe(monacoContext.require);
- });
-});
diff --git a/spec/javascripts/repo/stores/actions/branch_spec.js b/spec/javascripts/repo/stores/actions/branch_spec.js
deleted file mode 100644
index 00d16fd790d..00000000000
--- a/spec/javascripts/repo/stores/actions/branch_spec.js
+++ /dev/null
@@ -1,44 +0,0 @@
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { resetStore } from '../../helpers';
-
-describe('Multi-file store branch actions', () => {
- afterEach(() => {
- resetStore(store);
- });
-
- describe('createNewBranch', () => {
- beforeEach(() => {
- spyOn(service, 'createBranch').and.returnValue(Promise.resolve({
- json: () => ({
- name: 'testing',
- }),
- }));
- spyOn(history, 'pushState');
-
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'testing';
- store.state.projects.abcproject = {
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
- });
-
- it('creates new branch', (done) => {
- store.dispatch('createNewBranch', 'master')
- .then(() => {
- expect(store.state.currentBranchId).toBe('testing');
- expect(service.createBranch).toHaveBeenCalledWith('abcproject', {
- branch: 'master',
- ref: 'testing',
- });
-
- done();
- })
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/actions/file_spec.js b/spec/javascripts/repo/stores/actions/file_spec.js
deleted file mode 100644
index e2d8f002e27..00000000000
--- a/spec/javascripts/repo/stores/actions/file_spec.js
+++ /dev/null
@@ -1,431 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { file, resetStore } from '../../helpers';
-
-describe('Multi-file store file actions', () => {
- afterEach(() => {
- resetStore(store);
- });
-
- describe('closeFile', () => {
- let localFile;
- let getLastCommitDataSpy;
- let oldGetLastCommitData;
-
- beforeEach(() => {
- getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
- oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
- store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
-
- localFile = file('testFile');
- localFile.active = true;
- localFile.opened = true;
- localFile.parentTreeUrl = 'parentTreeUrl';
-
- store.state.openFiles.push(localFile);
- });
-
- afterEach(() => {
- store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
- });
-
- it('closes open files', (done) => {
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(localFile.opened).toBeFalsy();
- expect(localFile.active).toBeFalsy();
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
-
- it('does not close file if has changed', (done) => {
- localFile.changed = true;
-
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(localFile.opened).toBeTruthy();
- expect(localFile.active).toBeTruthy();
- expect(store.state.openFiles.length).toBe(1);
-
- done();
- }).catch(done.fail);
- });
-
- it('does not close file if temp file', (done) => {
- localFile.tempFile = true;
-
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(localFile.opened).toBeTruthy();
- expect(localFile.active).toBeTruthy();
- expect(store.state.openFiles.length).toBe(1);
-
- done();
- }).catch(done.fail);
- });
-
- it('force closes a changed file', (done) => {
- localFile.changed = true;
-
- store.dispatch('closeFile', { file: localFile, force: true })
- .then(() => {
- expect(localFile.opened).toBeFalsy();
- expect(localFile.active).toBeFalsy();
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
-
- it('sets next file as active', (done) => {
- const f = file('otherfile');
- store.state.openFiles.push(f);
-
- expect(f.active).toBeFalsy();
-
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(f.active).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('calls getLastCommitData', (done) => {
- store.dispatch('closeFile', { file: localFile })
- .then(() => {
- expect(getLastCommitDataSpy).toHaveBeenCalled();
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('setFileActive', () => {
- let scrollToTabSpy;
- let oldScrollToTab;
-
- beforeEach(() => {
- scrollToTabSpy = jasmine.createSpy('scrollToTab');
- oldScrollToTab = store._actions.scrollToTab; // eslint-disable-line
- store._actions.scrollToTab = [scrollToTabSpy]; // eslint-disable-line
- });
-
- afterEach(() => {
- store._actions.scrollToTab = oldScrollToTab; // eslint-disable-line
- });
-
- it('calls scrollToTab', (done) => {
- store.dispatch('setFileActive', file('setThisActive'))
- .then(() => {
- expect(scrollToTabSpy).toHaveBeenCalled();
-
- done();
- }).catch(done.fail);
- });
-
- it('sets the file active', (done) => {
- const localFile = file('activeFile');
-
- store.dispatch('setFileActive', localFile)
- .then(() => {
- expect(localFile.active).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('returns early if file is already active', (done) => {
- const localFile = file('earlyActive');
- localFile.active = true;
-
- store.dispatch('setFileActive', localFile)
- .then(() => {
- expect(scrollToTabSpy).not.toHaveBeenCalled();
-
- done();
- }).catch(done.fail);
- });
-
- it('sets current active file to not active', (done) => {
- const localFile = file('currentActive');
- localFile.active = true;
- store.state.openFiles.push(localFile);
-
- store.dispatch('setFileActive', file('newActive'))
- .then(() => {
- expect(localFile.active).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
- it('resets location.hash for line highlighting', (done) => {
- location.hash = 'test';
-
- store.dispatch('setFileActive', file('otherActive'))
- .then(() => {
- expect(location.hash).not.toBe('test');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('getFileData', () => {
- let localFile;
-
- beforeEach(() => {
- spyOn(service, 'getFileData').and.returnValue(Promise.resolve({
- headers: {
- 'page-title': 'testing getFileData',
- },
- json: () => Promise.resolve({
- blame_path: 'blame_path',
- commits_path: 'commits_path',
- permalink: 'permalink',
- raw_path: 'raw_path',
- binary: false,
- html: '123',
- render_error: '',
- }),
- }));
-
- localFile = file('newCreate');
- localFile.url = 'getFileDataURL';
- });
-
- afterEach(() => {
- store.dispatch('closeFile', {
- file: localFile,
- force: true,
- });
- });
-
- it('calls the service', (done) => {
- store.dispatch('getFileData', localFile)
- .then(() => {
- expect(service.getFileData).toHaveBeenCalledWith('getFileDataURL');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets the file data', (done) => {
- store.dispatch('getFileData', localFile)
- .then(Vue.nextTick)
- .then(() => {
- expect(localFile.blamePath).toBe('blame_path');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets document title', (done) => {
- store.dispatch('getFileData', localFile)
- .then(() => {
- expect(document.title).toBe('testing getFileData');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets the file as active', (done) => {
- store.dispatch('getFileData', localFile)
- .then(Vue.nextTick)
- .then(() => {
- expect(localFile.active).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('adds the file to open files', (done) => {
- store.dispatch('getFileData', localFile)
- .then(Vue.nextTick)
- .then(() => {
- expect(store.state.openFiles.length).toBe(1);
- expect(store.state.openFiles[0].name).toBe(localFile.name);
-
- done();
- }).catch(done.fail);
- });
-
- it('toggles the file loading', (done) => {
- store.dispatch('getFileData', localFile)
- .then(() => {
- expect(localFile.loading).toBeTruthy();
-
- return Vue.nextTick();
- })
- .then(() => {
- expect(localFile.loading).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('getRawFileData', () => {
- let tmpFile;
-
- beforeEach(() => {
- spyOn(service, 'getRawFileData').and.returnValue(Promise.resolve('raw'));
-
- tmpFile = file('tmpFile');
- });
-
- it('calls getRawFileData service method', (done) => {
- store.dispatch('getRawFileData', tmpFile)
- .then(() => {
- expect(service.getRawFileData).toHaveBeenCalledWith(tmpFile);
-
- done();
- }).catch(done.fail);
- });
-
- it('updates file raw data', (done) => {
- store.dispatch('getRawFileData', tmpFile)
- .then(() => {
- expect(tmpFile.raw).toBe('raw');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('changeFileContent', () => {
- let tmpFile;
-
- beforeEach(() => {
- tmpFile = file('tmpFile');
- });
-
- it('updates file content', (done) => {
- store.dispatch('changeFileContent', {
- file: tmpFile,
- content: 'content',
- })
- .then(() => {
- expect(tmpFile.content).toBe('content');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('createTempFile', () => {
- let projectTree;
-
- beforeEach(() => {
- document.body.innerHTML += '<div class="flash-container"></div>';
-
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
-
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
-
- projectTree = store.state.trees['abcproject/mybranch'];
- });
-
- afterEach(() => {
- document.querySelector('.flash-container').remove();
- });
-
- it('creates temp file', (done) => {
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then((f) => {
- expect(f.tempFile).toBeTruthy();
- expect(store.state.trees['abcproject/mybranch'].tree.length).toBe(1);
-
- done();
- }).catch(done.fail);
- });
-
- it('adds tmp file to open files', (done) => {
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then((f) => {
- expect(store.state.openFiles.length).toBe(1);
- expect(store.state.openFiles[0].name).toBe(f.name);
-
- done();
- }).catch(done.fail);
- });
-
- it('sets tmp file as active', (done) => {
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then((f) => {
- expect(f.active).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('enters edit mode if file is not base64', (done) => {
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then(() => {
- expect(store.state.editMode).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('creates flash message is file already exists', (done) => {
- store.state.trees['abcproject/mybranch'].tree.push(file('test', '1', 'blob'));
-
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then(() => {
- expect(document.querySelector('.flash-alert')).not.toBeNull();
-
- done();
- }).catch(done.fail);
- });
-
- it('increases level of file', (done) => {
- store.state.trees['abcproject/mybranch'].level = 1;
-
- store.dispatch('createTempFile', {
- name: 'test',
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- }).then((f) => {
- expect(f.level).toBe(2);
-
- done();
- }).catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/actions/tree_spec.js b/spec/javascripts/repo/stores/actions/tree_spec.js
deleted file mode 100644
index 65351dbb7d9..00000000000
--- a/spec/javascripts/repo/stores/actions/tree_spec.js
+++ /dev/null
@@ -1,350 +0,0 @@
-import Vue from 'vue';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { file, resetStore } from '../../helpers';
-
-describe('Multi-file store tree actions', () => {
- let projectTree;
-
- const basicCallParameters = {
- endpoint: 'rootEndpoint',
- projectId: 'abcproject',
- branch: 'master',
- };
-
- beforeEach(() => {
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- web_url: '',
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
- });
-
- afterEach(() => {
- resetStore(store);
- });
-
- describe('getTreeData', () => {
- beforeEach(() => {
- spyOn(service, 'getTreeData').and.returnValue(Promise.resolve({
- headers: {
- 'page-title': 'test',
- },
- json: () => Promise.resolve({
- last_commit_path: 'last_commit_path',
- parent_tree_url: 'parent_tree_url',
- path: '/',
- trees: [{ name: 'tree' }],
- blobs: [{ name: 'blob' }],
- submodules: [{ name: 'submodule' }],
- }),
- }));
- });
-
- it('calls service getTreeData', (done) => {
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(service.getTreeData).toHaveBeenCalledWith('rootEndpoint');
-
- done();
- }).catch(done.fail);
- });
-
- it('adds data into tree', (done) => {
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- projectTree = store.state.trees['abcproject/master'];
- expect(projectTree.tree.length).toBe(3);
- expect(projectTree.tree[0].type).toBe('tree');
- expect(projectTree.tree[1].type).toBe('submodule');
- expect(projectTree.tree[2].type).toBe('blob');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets parent tree URL', (done) => {
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(store.state.parentTreeUrl).toBe('parent_tree_url');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets last commit path', (done) => {
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(store.state.trees['abcproject/master'].lastCommitPath).toBe('last_commit_path');
-
- done();
- }).catch(done.fail);
- });
-
- it('sets root if not currently at root', (done) => {
- store.state.isInitialRoot = false;
-
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(store.state.isInitialRoot).toBeTruthy();
- expect(store.state.isRoot).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('sets page title', (done) => {
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(document.title).toBe('test');
-
- done();
- }).catch(done.fail);
- });
-
- it('calls getLastCommitData if prevLastCommitPath is not null', (done) => {
- const getLastCommitDataSpy = jasmine.createSpy('getLastCommitData');
- const oldGetLastCommitData = store._actions.getLastCommitData; // eslint-disable-line
- store._actions.getLastCommitData = [getLastCommitDataSpy]; // eslint-disable-line
- store.state.prevLastCommitPath = 'test';
-
- store.dispatch('getTreeData', basicCallParameters)
- .then(() => {
- expect(getLastCommitDataSpy).toHaveBeenCalledWith(projectTree);
-
- store._actions.getLastCommitData = oldGetLastCommitData; // eslint-disable-line
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('toggleTreeOpen', () => {
- let oldGetTreeData;
- let getTreeDataSpy;
- let tree;
-
- beforeEach(() => {
- getTreeDataSpy = jasmine.createSpy('getTreeData');
-
- oldGetTreeData = store._actions.getTreeData; // eslint-disable-line
- store._actions.getTreeData = [getTreeDataSpy]; // eslint-disable-line
-
- tree = {
- projectId: 'abcproject',
- branchId: 'master',
- opened: false,
- tree: [],
- };
- });
-
- afterEach(() => {
- store._actions.getTreeData = oldGetTreeData; // eslint-disable-line
- });
-
- it('toggles the tree open', (done) => {
- store.dispatch('toggleTreeOpen', {
- endpoint: 'test',
- tree,
- }).then(() => {
- expect(tree.opened).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('calls getTreeData if tree is closed', (done) => {
- store.dispatch('toggleTreeOpen', {
- endpoint: 'test',
- tree,
- }).then(() => {
- expect(getTreeDataSpy).toHaveBeenCalledWith({
- projectId: 'abcproject',
- branch: 'master',
- endpoint: 'test',
- tree,
- });
-
- done();
- }).catch(done.fail);
- });
-
- it('resets entries tree', (done) => {
- Object.assign(tree, {
- opened: true,
- tree: ['a'],
- });
-
- store.dispatch('toggleTreeOpen', {
- endpoint: 'test',
- tree,
- }).then(() => {
- expect(tree.tree.length).toBe(0);
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('createTempTree', () => {
- beforeEach(() => {
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
- projectTree = store.state.trees['abcproject/mybranch'];
- });
-
- it('creates temp tree', (done) => {
- store.dispatch('createTempTree', {
- projectId: store.state.currentProjectId,
- branchId: store.state.currentBranchId,
- name: 'test',
- parent: projectTree,
- })
- .then(() => {
- expect(projectTree.tree[0].name).toBe('test');
- expect(projectTree.tree[0].type).toBe('tree');
-
- done();
- }).catch(done.fail);
- });
-
- it('creates new folder inside another tree', (done) => {
- const tree = {
- type: 'tree',
- name: 'testing',
- tree: [],
- };
-
- projectTree.tree.push(tree);
-
- store.dispatch('createTempTree', {
- projectId: store.state.currentProjectId,
- branchId: store.state.currentBranchId,
- name: 'testing/test',
- parent: projectTree,
- })
- .then(() => {
- expect(projectTree.tree[0].name).toBe('testing');
- expect(projectTree.tree[0].tree[0].tempFile).toBeTruthy();
- expect(projectTree.tree[0].tree[0].name).toBe('test');
- expect(projectTree.tree[0].tree[0].type).toBe('tree');
-
- done();
- }).catch(done.fail);
- });
-
- it('does not create new tree if already exists', (done) => {
- const tree = {
- type: 'tree',
- name: 'testing',
- endpoint: 'test',
- tree: [],
- };
-
- projectTree.tree.push(tree);
-
- store.dispatch('createTempTree', {
- projectId: store.state.currentProjectId,
- branchId: store.state.currentBranchId,
- name: 'testing/test',
- parent: projectTree,
- })
- .then(() => {
- expect(projectTree.tree[0].name).toBe('testing');
- expect(projectTree.tree[0].tempFile).toBeUndefined();
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('getLastCommitData', () => {
- beforeEach(() => {
- spyOn(service, 'getTreeLastCommit').and.returnValue(Promise.resolve({
- headers: {
- 'more-logs-url': null,
- },
- json: () => Promise.resolve([{
- type: 'tree',
- file_name: 'testing',
- commit: {
- message: 'commit message',
- authored_date: '123',
- },
- }]),
- }));
-
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
-
- projectTree = store.state.trees['abcproject/mybranch'];
- projectTree.tree.push(file('testing', '1', 'tree'));
- projectTree.lastCommitPath = 'lastcommitpath';
- });
-
- it('calls service with lastCommitPath', (done) => {
- store.dispatch('getLastCommitData', projectTree)
- .then(() => {
- expect(service.getTreeLastCommit).toHaveBeenCalledWith('lastcommitpath');
-
- done();
- }).catch(done.fail);
- });
-
- it('updates trees last commit data', (done) => {
- store.dispatch('getLastCommitData', projectTree)
- .then(Vue.nextTick)
- .then(() => {
- expect(projectTree.tree[0].lastCommit.message).toBe('commit message');
-
- done();
- }).catch(done.fail);
- });
-
- it('does not update entry if not found', (done) => {
- projectTree.tree[0].name = 'a';
-
- store.dispatch('getLastCommitData', projectTree)
- .then(Vue.nextTick)
- .then(() => {
- expect(projectTree.tree[0].lastCommit.message).not.toBe('commit message');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('updateDirectoryData', () => {
- it('adds data into tree', (done) => {
- const tree = {
- tree: [],
- };
- const data = {
- trees: [{ name: 'tree' }],
- submodules: [{ name: 'submodule' }],
- blobs: [{ name: 'blob' }],
- };
-
- store.dispatch('updateDirectoryData', {
- data,
- tree,
- }).then(() => {
- expect(tree.tree[0].name).toBe('tree');
- expect(tree.tree[0].type).toBe('tree');
- expect(tree.tree[1].name).toBe('submodule');
- expect(tree.tree[1].type).toBe('submodule');
- expect(tree.tree[2].name).toBe('blob');
- expect(tree.tree[2].type).toBe('blob');
-
- done();
- }).catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/actions_spec.js b/spec/javascripts/repo/stores/actions_spec.js
deleted file mode 100644
index f678967b092..00000000000
--- a/spec/javascripts/repo/stores/actions_spec.js
+++ /dev/null
@@ -1,432 +0,0 @@
-import Vue from 'vue';
-import * as urlUtils from '~/lib/utils/url_utility';
-import store from '~/ide/stores';
-import service from '~/ide/services';
-import { resetStore, file } from '../helpers';
-
-describe('Multi-file store actions', () => {
- afterEach(() => {
- resetStore(store);
- });
-
- describe('redirectToUrl', () => {
- it('calls visitUrl', (done) => {
- spyOn(urlUtils, 'visitUrl');
-
- store.dispatch('redirectToUrl', 'test')
- .then(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith('test');
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('setInitialData', () => {
- it('commits initial data', (done) => {
- store.dispatch('setInitialData', { canCommit: true })
- .then(() => {
- expect(store.state.canCommit).toBeTruthy();
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('closeDiscardPopup', () => {
- it('closes the discard popup', (done) => {
- store.dispatch('closeDiscardPopup', false)
- .then(() => {
- expect(store.state.discardPopupOpen).toBeFalsy();
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('discardAllChanges', () => {
- beforeEach(() => {
- store.state.openFiles.push(file('discardAll'));
- store.state.openFiles[0].changed = true;
- });
- });
-
- describe('closeAllFiles', () => {
- beforeEach(() => {
- store.state.openFiles.push(file('closeAll'));
- store.state.openFiles[0].opened = true;
- });
-
- it('closes all open files', (done) => {
- store.dispatch('closeAllFiles')
- .then(() => {
- expect(store.state.openFiles.length).toBe(0);
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('toggleEditMode', () => {
- it('toggles edit mode', (done) => {
- store.state.editMode = true;
-
- store.dispatch('toggleEditMode')
- .then(() => {
- expect(store.state.editMode).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
- it('sets preview mode', (done) => {
- store.state.currentBlobView = 'repo-editor';
- store.state.editMode = true;
-
- store.dispatch('toggleEditMode')
- .then(Vue.nextTick)
- .then(() => {
- expect(store.state.currentBlobView).toBe('repo-preview');
-
- done();
- }).catch(done.fail);
- });
-
- it('opens discard popup if there are changed files', (done) => {
- store.state.editMode = true;
- store.state.openFiles.push(file('discardChanges'));
- store.state.openFiles[0].changed = true;
-
- store.dispatch('toggleEditMode')
- .then(() => {
- expect(store.state.discardPopupOpen).toBeTruthy();
-
- done();
- }).catch(done.fail);
- });
-
- it('can force closed if there are changed files', (done) => {
- store.state.editMode = true;
-
- store.state.openFiles.push(file('forceClose'));
- store.state.openFiles[0].changed = true;
-
- store.dispatch('toggleEditMode', true)
- .then(() => {
- expect(store.state.discardPopupOpen).toBeFalsy();
- expect(store.state.editMode).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
-
- it('discards file changes', (done) => {
- const f = file('discard');
- store.state.editMode = true;
- store.state.openFiles.push(f);
- f.changed = true;
-
- store.dispatch('toggleEditMode', true)
- .then(Vue.nextTick)
- .then(() => {
- expect(f.changed).toBeFalsy();
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('toggleBlobView', () => {
- it('sets edit mode view if in edit mode', (done) => {
- store.dispatch('toggleBlobView')
- .then(() => {
- expect(store.state.currentBlobView).toBe('repo-editor');
-
- done();
- })
- .catch(done.fail);
- });
-
- it('sets preview mode view if not in edit mode', (done) => {
- store.state.editMode = false;
-
- store.dispatch('toggleBlobView')
- .then(() => {
- expect(store.state.currentBlobView).toBe('repo-preview');
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('checkCommitStatus', () => {
- beforeEach(() => {
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
- });
-
- it('calls service', (done) => {
- spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- data: {
- commit: { id: '123' },
- },
- }));
-
- store.dispatch('checkCommitStatus')
- .then(() => {
- expect(service.getBranchData).toHaveBeenCalledWith('abcproject', 'master');
-
- done();
- })
- .catch(done.fail);
- });
-
- it('returns true if current ref does not equal returned ID', (done) => {
- spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- data: {
- commit: { id: '123' },
- },
- }));
-
- store.dispatch('checkCommitStatus')
- .then((val) => {
- expect(val).toBeTruthy();
-
- done();
- })
- .catch(done.fail);
- });
-
- it('returns false if current ref equals returned ID', (done) => {
- spyOn(service, 'getBranchData').and.returnValue(Promise.resolve({
- data: {
- commit: { id: '1' },
- },
- }));
-
- store.dispatch('checkCommitStatus')
- .then((val) => {
- expect(val).toBeFalsy();
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('commitChanges', () => {
- let payload;
-
- beforeEach(() => {
- spyOn(window, 'scrollTo');
-
- document.body.innerHTML += '<div class="flash-container"></div>';
-
- store.state.currentProjectId = 'abcproject';
- store.state.currentBranchId = 'master';
- store.state.projects.abcproject = {
- web_url: 'webUrl',
- branches: {
- master: {
- workingReference: '1',
- },
- },
- };
-
- payload = {
- branch: 'master',
- };
- });
-
- afterEach(() => {
- document.querySelector('.flash-container').remove();
- });
-
- describe('success', () => {
- beforeEach(() => {
- spyOn(service, 'commit').and.returnValue(Promise.resolve({
- data: {
- id: '123456',
- short_id: '123',
- message: 'test message',
- committed_date: 'date',
- stats: {
- additions: '1',
- deletions: '2',
- },
- },
- }));
- });
-
- it('calls service', (done) => {
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- expect(service.commit).toHaveBeenCalledWith('abcproject', payload);
-
- done();
- }).catch(done.fail);
- });
-
- it('shows flash notice', (done) => {
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- const alert = document.querySelector('.flash-container');
-
- expect(alert.querySelector('.flash-notice')).not.toBeNull();
- expect(alert.textContent.trim()).toBe(
- 'Your changes have been committed. Commit 123 with 1 additions, 2 deletions.',
- );
-
- done();
- }).catch(done.fail);
- });
-
- it('adds commit data to changed files', (done) => {
- const changedFile = file('changed');
- const f = file('newfile');
- changedFile.changed = true;
-
- store.state.openFiles.push(changedFile, f);
-
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- expect(changedFile.lastCommit.message).toBe('test message');
- expect(f.lastCommit.message).not.toBe('test message');
-
- done();
- }).catch(done.fail);
- });
-
- it('scrolls to top of page', (done) => {
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- expect(window.scrollTo).toHaveBeenCalledWith(0, 0);
-
- done();
- }).catch(done.fail);
- });
-
- it('redirects to new merge request page', (done) => {
- spyOn(urlUtils, 'visitUrl');
-
- store.dispatch('commitChanges', { payload, newMr: true })
- .then(() => {
- expect(urlUtils.visitUrl).toHaveBeenCalledWith('webUrl/merge_requests/new?merge_request%5Bsource_branch%5D=master');
-
- done();
- }).catch(done.fail);
- });
- });
-
- describe('failed', () => {
- beforeEach(() => {
- spyOn(service, 'commit').and.returnValue(Promise.resolve({
- data: {
- message: 'failed message',
- },
- }));
- });
-
- it('shows failed message', (done) => {
- store.dispatch('commitChanges', { payload, newMr: false })
- .then(() => {
- const alert = document.querySelector('.flash-container');
-
- expect(alert.textContent.trim()).toBe(
- 'failed message',
- );
-
- done();
- }).catch(done.fail);
- });
- });
- });
-
- describe('createTempEntry', () => {
- beforeEach(() => {
- store.state.trees['abcproject/mybranch'] = {
- tree: [],
- };
- store.state.projects.abcproject = {
- web_url: '',
- };
- });
-
- it('creates a temp tree', (done) => {
- const projectTree = store.state.trees['abcproject/mybranch'];
-
- store.dispatch('createTempEntry', {
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- name: 'test',
- type: 'tree',
- })
- .then(() => {
- const baseTree = projectTree.tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].tempFile).toBeTruthy();
- expect(baseTree[0].type).toBe('tree');
-
- done();
- })
- .catch(done.fail);
- });
-
- it('creates temp file', (done) => {
- const projectTree = store.state.trees['abcproject/mybranch'];
-
- store.dispatch('createTempEntry', {
- projectId: 'abcproject',
- branchId: 'mybranch',
- parent: projectTree,
- name: 'test',
- type: 'blob',
- })
- .then(() => {
- const baseTree = projectTree.tree;
- expect(baseTree.length).toBe(1);
- expect(baseTree[0].tempFile).toBeTruthy();
- expect(baseTree[0].type).toBe('blob');
-
- done();
- })
- .catch(done.fail);
- });
- });
-
- describe('popHistoryState', () => {
-
- });
-
- describe('scrollToTab', () => {
- it('focuses the current active element', (done) => {
- document.body.innerHTML += '<div id="tabs"><div class="active"><div class="repo-tab"></div></div></div>';
- const el = document.querySelector('.repo-tab');
- spyOn(el, 'focus');
-
- store.dispatch('scrollToTab')
- .then(() => {
- setTimeout(() => {
- expect(el.focus).toHaveBeenCalled();
-
- document.getElementById('tabs').remove();
-
- done();
- });
- })
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/getters_spec.js b/spec/javascripts/repo/stores/getters_spec.js
deleted file mode 100644
index d0d5934f29a..00000000000
--- a/spec/javascripts/repo/stores/getters_spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import * as getters from '~/ide/stores/getters';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store getters', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- });
-
- describe('changedFiles', () => {
- it('returns a list of changed opened files', () => {
- localState.openFiles.push(file());
- localState.openFiles.push(file('changed'));
- localState.openFiles[1].changed = true;
-
- const changedFiles = getters.changedFiles(localState);
-
- expect(changedFiles.length).toBe(1);
- expect(changedFiles[0].name).toBe('changed');
- });
- });
-
- describe('activeFile', () => {
- it('returns the current active file', () => {
- localState.openFiles.push(file());
- localState.openFiles.push(file('active'));
- localState.openFiles[1].active = true;
-
- expect(getters.activeFile(localState).name).toBe('active');
- });
-
- it('returns undefined if no active files are found', () => {
- localState.openFiles.push(file());
- localState.openFiles.push(file('active'));
-
- expect(getters.activeFile(localState)).toBeNull();
- });
- });
-
- describe('activeFileExtension', () => {
- it('returns the file extension for the current active file', () => {
- localState.openFiles.push(file('active'));
- localState.openFiles[0].active = true;
- localState.openFiles[0].path = 'test.js';
-
- expect(getters.activeFileExtension(localState)).toBe('.js');
-
- localState.openFiles[0].path = 'test.es6.js';
-
- expect(getters.activeFileExtension(localState)).toBe('.js');
- });
- });
-
- describe('canEditFile', () => {
- beforeEach(() => {
- localState.onTopOfBranch = true;
- localState.canCommit = true;
-
- localState.openFiles.push(file());
- localState.openFiles[0].active = true;
- });
-
- it('returns true if user can commit and has open files', () => {
- expect(getters.canEditFile(localState)).toBeTruthy();
- });
-
- it('returns false if user can commit and has no open files', () => {
- localState.openFiles = [];
-
- expect(getters.canEditFile(localState)).toBeFalsy();
- });
-
- it('returns false if user can commit and active file is binary', () => {
- localState.openFiles[0].binary = true;
-
- expect(getters.canEditFile(localState)).toBeFalsy();
- });
-
- it('returns false if user cant commit', () => {
- localState.canCommit = false;
-
- expect(getters.canEditFile(localState)).toBeFalsy();
- });
- });
-
- describe('modifiedFiles', () => {
- it('returns a list of modified files', () => {
- localState.openFiles.push(file());
- localState.openFiles.push(file('changed'));
- localState.openFiles[1].changed = true;
-
- const modifiedFiles = getters.modifiedFiles(localState);
-
- expect(modifiedFiles.length).toBe(1);
- expect(modifiedFiles[0].name).toBe('changed');
- });
- });
-
- describe('addedFiles', () => {
- it('returns a list of added files', () => {
- localState.openFiles.push(file());
- localState.openFiles.push(file('added'));
- localState.openFiles[1].changed = true;
- localState.openFiles[1].tempFile = true;
-
- const modifiedFiles = getters.addedFiles(localState);
-
- expect(modifiedFiles.length).toBe(1);
- expect(modifiedFiles[0].name).toBe('added');
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/mutations/branch_spec.js b/spec/javascripts/repo/stores/mutations/branch_spec.js
deleted file mode 100644
index a7167537ef2..00000000000
--- a/spec/javascripts/repo/stores/mutations/branch_spec.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import mutations from '~/ide/stores/mutations/branch';
-import state from '~/ide/stores/state';
-
-describe('Multi-file store branch mutations', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- });
-
- describe('SET_CURRENT_BRANCH', () => {
- it('sets currentBranch', () => {
- mutations.SET_CURRENT_BRANCH(localState, 'master');
-
- expect(localState.currentBranchId).toBe('master');
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/mutations/file_spec.js b/spec/javascripts/repo/stores/mutations/file_spec.js
deleted file mode 100644
index 6e204ef0404..00000000000
--- a/spec/javascripts/repo/stores/mutations/file_spec.js
+++ /dev/null
@@ -1,131 +0,0 @@
-import mutations from '~/ide/stores/mutations/file';
-import state from '~/ide/stores/state';
-import { file } from '../../helpers';
-
-describe('Multi-file store file mutations', () => {
- let localState;
- let localFile;
-
- beforeEach(() => {
- localState = state();
- localFile = file();
- });
-
- describe('SET_FILE_ACTIVE', () => {
- it('sets the file active', () => {
- mutations.SET_FILE_ACTIVE(localState, {
- file: localFile,
- active: true,
- });
-
- expect(localFile.active).toBeTruthy();
- });
- });
-
- describe('TOGGLE_FILE_OPEN', () => {
- beforeEach(() => {
- mutations.TOGGLE_FILE_OPEN(localState, localFile);
- });
-
- it('adds into opened files', () => {
- expect(localFile.opened).toBeTruthy();
- expect(localState.openFiles.length).toBe(1);
- });
-
- it('removes from opened files', () => {
- mutations.TOGGLE_FILE_OPEN(localState, localFile);
-
- expect(localFile.opened).toBeFalsy();
- expect(localState.openFiles.length).toBe(0);
- });
- });
-
- describe('SET_FILE_DATA', () => {
- it('sets extra file data', () => {
- mutations.SET_FILE_DATA(localState, {
- data: {
- blame_path: 'blame',
- commits_path: 'commits',
- permalink: 'permalink',
- raw_path: 'raw',
- binary: true,
- html: 'html',
- render_error: 'render_error',
- },
- file: localFile,
- });
-
- expect(localFile.blamePath).toBe('blame');
- expect(localFile.commitsPath).toBe('commits');
- expect(localFile.permalink).toBe('permalink');
- expect(localFile.rawPath).toBe('raw');
- expect(localFile.binary).toBeTruthy();
- expect(localFile.html).toBe('html');
- expect(localFile.renderError).toBe('render_error');
- });
- });
-
- describe('SET_FILE_RAW_DATA', () => {
- it('sets raw data', () => {
- mutations.SET_FILE_RAW_DATA(localState, {
- file: localFile,
- raw: 'testing',
- });
-
- expect(localFile.raw).toBe('testing');
- });
- });
-
- describe('UPDATE_FILE_CONTENT', () => {
- beforeEach(() => {
- localFile.raw = 'test';
- });
-
- it('sets content', () => {
- mutations.UPDATE_FILE_CONTENT(localState, {
- file: localFile,
- content: 'test',
- });
-
- expect(localFile.content).toBe('test');
- });
-
- it('sets changed if content does not match raw', () => {
- mutations.UPDATE_FILE_CONTENT(localState, {
- file: localFile,
- content: 'testing',
- });
-
- expect(localFile.content).toBe('testing');
- expect(localFile.changed).toBeTruthy();
- });
- });
-
- describe('DISCARD_FILE_CHANGES', () => {
- beforeEach(() => {
- localFile.content = 'test';
- localFile.changed = true;
- });
-
- it('resets content and changed', () => {
- mutations.DISCARD_FILE_CHANGES(localState, localFile);
-
- expect(localFile.content).toBe('');
- expect(localFile.changed).toBeFalsy();
- });
- });
-
- describe('CREATE_TMP_FILE', () => {
- it('adds file into parent tree', () => {
- const f = file('tmpFile');
-
- mutations.CREATE_TMP_FILE(localState, {
- file: f,
- parent: localFile,
- });
-
- expect(localFile.tree.length).toBe(1);
- expect(localFile.tree[0].name).toBe(f.name);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/mutations/tree_spec.js b/spec/javascripts/repo/stores/mutations/tree_spec.js
deleted file mode 100644
index e6ca8ea139e..00000000000
--- a/spec/javascripts/repo/stores/mutations/tree_spec.js
+++ /dev/null
@@ -1,71 +0,0 @@
-import mutations from '~/ide/stores/mutations/tree';
-import state from '~/ide/stores/state';
-import { file } from '../../helpers';
-
-describe('Multi-file store tree mutations', () => {
- let localState;
- let localTree;
-
- beforeEach(() => {
- localState = state();
- localTree = file();
- });
-
- describe('TOGGLE_TREE_OPEN', () => {
- it('toggles tree open', () => {
- mutations.TOGGLE_TREE_OPEN(localState, localTree);
-
- expect(localTree.opened).toBeTruthy();
-
- mutations.TOGGLE_TREE_OPEN(localState, localTree);
-
- expect(localTree.opened).toBeFalsy();
- });
- });
-
- describe('SET_DIRECTORY_DATA', () => {
- const data = [{
- name: 'tree',
- },
- {
- name: 'submodule',
- },
- {
- name: 'blob',
- }];
-
- it('adds directory data', () => {
- mutations.SET_DIRECTORY_DATA(localState, {
- data,
- tree: localState,
- });
-
- expect(localState.tree.length).toBe(3);
- expect(localState.tree[0].name).toBe('tree');
- expect(localState.tree[1].name).toBe('submodule');
- expect(localState.tree[2].name).toBe('blob');
- });
- });
-
- describe('SET_PARENT_TREE_URL', () => {
- it('sets the parent tree url', () => {
- mutations.SET_PARENT_TREE_URL(localState, 'test');
-
- expect(localState.parentTreeUrl).toBe('test');
- });
- });
-
- describe('CREATE_TMP_TREE', () => {
- it('adds tree into parent tree', () => {
- const tmpEntry = file('tmpTree');
-
- mutations.CREATE_TMP_TREE(localState, {
- tmpEntry,
- parent: localTree,
- });
-
- expect(localTree.tree.length).toBe(1);
- expect(localTree.tree[0].name).toBe(tmpEntry.name);
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/mutations_spec.js b/spec/javascripts/repo/stores/mutations_spec.js
deleted file mode 100644
index 5fd8ad94972..00000000000
--- a/spec/javascripts/repo/stores/mutations_spec.js
+++ /dev/null
@@ -1,125 +0,0 @@
-import mutations from '~/ide/stores/mutations';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store mutations', () => {
- let localState;
- let entry;
-
- beforeEach(() => {
- localState = state();
- entry = file();
- });
-
- describe('SET_INITIAL_DATA', () => {
- it('sets all initial data', () => {
- mutations.SET_INITIAL_DATA(localState, {
- test: 'test',
- });
-
- expect(localState.test).toBe('test');
- });
- });
-
- describe('SET_PREVIEW_MODE', () => {
- it('sets currentBlobView to repo-preview', () => {
- mutations.SET_PREVIEW_MODE(localState);
-
- expect(localState.currentBlobView).toBe('repo-preview');
-
- localState.currentBlobView = 'testing';
-
- mutations.SET_PREVIEW_MODE(localState);
-
- expect(localState.currentBlobView).toBe('repo-preview');
- });
- });
-
- describe('SET_EDIT_MODE', () => {
- it('sets currentBlobView to repo-editor', () => {
- mutations.SET_EDIT_MODE(localState);
-
- expect(localState.currentBlobView).toBe('repo-editor');
-
- localState.currentBlobView = 'testing';
-
- mutations.SET_EDIT_MODE(localState);
-
- expect(localState.currentBlobView).toBe('repo-editor');
- });
- });
-
- describe('TOGGLE_LOADING', () => {
- it('toggles loading of entry', () => {
- mutations.TOGGLE_LOADING(localState, entry);
-
- expect(entry.loading).toBeTruthy();
-
- mutations.TOGGLE_LOADING(localState, entry);
-
- expect(entry.loading).toBeFalsy();
- });
- });
-
- describe('TOGGLE_EDIT_MODE', () => {
- it('toggles editMode', () => {
- mutations.TOGGLE_EDIT_MODE(localState);
-
- expect(localState.editMode).toBeFalsy();
-
- mutations.TOGGLE_EDIT_MODE(localState);
-
- expect(localState.editMode).toBeTruthy();
- });
- });
-
- describe('TOGGLE_DISCARD_POPUP', () => {
- it('sets discardPopupOpen', () => {
- mutations.TOGGLE_DISCARD_POPUP(localState, true);
-
- expect(localState.discardPopupOpen).toBeTruthy();
-
- mutations.TOGGLE_DISCARD_POPUP(localState, false);
-
- expect(localState.discardPopupOpen).toBeFalsy();
- });
- });
-
- describe('SET_ROOT', () => {
- it('sets isRoot & initialRoot', () => {
- mutations.SET_ROOT(localState, true);
-
- expect(localState.isRoot).toBeTruthy();
- expect(localState.isInitialRoot).toBeTruthy();
-
- mutations.SET_ROOT(localState, false);
-
- expect(localState.isRoot).toBeFalsy();
- expect(localState.isInitialRoot).toBeFalsy();
- });
- });
-
- describe('SET_LEFT_PANEL_COLLAPSED', () => {
- it('sets left panel collapsed', () => {
- mutations.SET_LEFT_PANEL_COLLAPSED(localState, true);
-
- expect(localState.leftPanelCollapsed).toBeTruthy();
-
- mutations.SET_LEFT_PANEL_COLLAPSED(localState, false);
-
- expect(localState.leftPanelCollapsed).toBeFalsy();
- });
- });
-
- describe('SET_RIGHT_PANEL_COLLAPSED', () => {
- it('sets right panel collapsed', () => {
- mutations.SET_RIGHT_PANEL_COLLAPSED(localState, true);
-
- expect(localState.rightPanelCollapsed).toBeTruthy();
-
- mutations.SET_RIGHT_PANEL_COLLAPSED(localState, false);
-
- expect(localState.rightPanelCollapsed).toBeFalsy();
- });
- });
-});
diff --git a/spec/javascripts/repo/stores/utils_spec.js b/spec/javascripts/repo/stores/utils_spec.js
deleted file mode 100644
index 89745a2029e..00000000000
--- a/spec/javascripts/repo/stores/utils_spec.js
+++ /dev/null
@@ -1,119 +0,0 @@
-import * as utils from '~/ide/stores/utils';
-import state from '~/ide/stores/state';
-import { file } from '../helpers';
-
-describe('Multi-file store utils', () => {
- describe('setPageTitle', () => {
- it('sets the document page title', () => {
- utils.setPageTitle('test');
-
- expect(document.title).toBe('test');
- });
- });
-
- describe('treeList', () => {
- let localState;
-
- beforeEach(() => {
- localState = state();
- });
-
- it('returns flat tree list', () => {
- localState.trees = [];
- localState.trees['abcproject/mybranch'] = {
- tree: [],
- };
- const baseTree = localState.trees['abcproject/mybranch'].tree;
- baseTree.push(file('1'));
- baseTree[0].tree.push(file('2'));
- baseTree[0].tree[0].tree.push(file('3'));
-
- const treeList = utils.treeList(localState, 'abcproject/mybranch');
-
- expect(treeList.length).toBe(3);
- expect(treeList[1].name).toBe(baseTree[0].tree[0].name);
- expect(treeList[2].name).toBe(baseTree[0].tree[0].tree[0].name);
- });
- });
-
- describe('createTemp', () => {
- it('creates temp tree', () => {
- const tmp = utils.createTemp({
- name: 'test',
- path: 'test',
- type: 'tree',
- level: 0,
- changed: false,
- content: '',
- base64: '',
- });
-
- expect(tmp.tempFile).toBeTruthy();
- expect(tmp.icon).toBe('fa-folder');
- });
-
- it('creates temp file', () => {
- const tmp = utils.createTemp({
- name: 'test',
- path: 'test',
- type: 'blob',
- level: 0,
- changed: false,
- content: '',
- base64: '',
- });
-
- expect(tmp.tempFile).toBeTruthy();
- expect(tmp.icon).toBe('fa-file-text-o');
- });
- });
-
- describe('findIndexOfFile', () => {
- let localState;
-
- beforeEach(() => {
- localState = [{
- path: '1',
- }, {
- path: '2',
- }];
- });
-
- it('finds in the index of an entry by path', () => {
- const index = utils.findIndexOfFile(localState, {
- path: '2',
- });
-
- expect(index).toBe(1);
- });
- });
-
- describe('findEntry', () => {
- let localState;
-
- beforeEach(() => {
- localState = {
- tree: [{
- type: 'tree',
- name: 'test',
- }, {
- type: 'blob',
- name: 'file',
- }],
- };
- });
-
- it('returns an entry found by name', () => {
- const foundEntry = utils.findEntry(localState.tree, 'tree', 'test');
-
- expect(foundEntry.type).toBe('tree');
- expect(foundEntry.name).toBe('test');
- });
-
- it('returns undefined when no entry found', () => {
- const foundEntry = utils.findEntry(localState.tree, 'blob', 'test');
-
- expect(foundEntry).toBeUndefined();
- });
- });
-});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
new file mode 100644
index 00000000000..67056793a20
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -0,0 +1,81 @@
+import Vue from 'vue';
+
+import LabelsSelect from '~/labels_select';
+import baseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (config = mockConfig) => {
+ const Component = Vue.extend(baseComponent);
+
+ return mountComponent(Component, config);
+};
+
+describe('BaseComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('hiddenInputName', () => {
+ it('returns correct string when showCreate prop is `true`', () => {
+ expect(vm.hiddenInputName).toBe('issue[label_names][]');
+ });
+
+ it('returns correct string when showCreate prop is `false`', () => {
+ const mockConfigNonEditable = Object.assign({}, mockConfig, { showCreate: false });
+ const vmNonEditable = createComponent(mockConfigNonEditable);
+ expect(vmNonEditable.hiddenInputName).toBe('label_id[]');
+ vmNonEditable.$destroy();
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('handleClick', () => {
+ it('emits onLabelClick event with label and list of labels as params', () => {
+ spyOn(vm, '$emit');
+ vm.handleClick(mockLabels[0]);
+ expect(vm.$emit).toHaveBeenCalledWith('onLabelClick', mockLabels[0]);
+ });
+ });
+ });
+
+ describe('mounted', () => {
+ it('creates LabelsSelect object and assigns it to `labelsDropdon` as prop', () => {
+ expect(vm.labelsDropdown instanceof LabelsSelect).toBe(true);
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `block labels`', () => {
+ expect(vm.$el.classList.contains('block')).toBe(true);
+ expect(vm.$el.classList.contains('labels')).toBe(true);
+ });
+
+ it('renders `.selectbox` element', () => {
+ expect(vm.$el.querySelector('.selectbox')).not.toBeNull();
+ expect(vm.$el.querySelector('.selectbox').getAttribute('style')).toBe('display: none;');
+ });
+
+ it('renders `.dropdown` element', () => {
+ expect(vm.$el.querySelector('.dropdown')).not.toBeNull();
+ });
+
+ it('renders `.dropdown-menu` element', () => {
+ const dropdownMenuEl = vm.$el.querySelector('.dropdown-menu');
+ expect(dropdownMenuEl).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-page-one')).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-content')).not.toBeNull();
+ expect(dropdownMenuEl.querySelector('.dropdown-loading')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
new file mode 100644
index 00000000000..ec63ac306d0
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -0,0 +1,82 @@
+import Vue from 'vue';
+
+import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const componentConfig = Object.assign({}, mockConfig, {
+ fieldName: 'label_id[]',
+ labels: mockLabels,
+ showExtraOptions: false,
+});
+
+const createComponent = (config = componentConfig) => {
+ const Component = Vue.extend(dropdownButtonComponent);
+
+ return mountComponent(Component, config);
+};
+
+describe('DropdownButtonComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('dropdownToggleText', () => {
+ it('returns text as `Label` when `labels` prop is empty array', () => {
+ const mockEmptyLabels = Object.assign({}, componentConfig, { labels: [] });
+ const vmEmptyLabels = createComponent(mockEmptyLabels);
+ expect(vmEmptyLabels.dropdownToggleText).toBe('Label');
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns first label name with remaining label count when `labels` prop has more than one item', () => {
+ const mockMoreLabels = Object.assign({}, componentConfig, {
+ labels: mockLabels.concat(mockLabels),
+ });
+ const vmMoreLabels = createComponent(mockMoreLabels);
+ expect(vmMoreLabels.dropdownToggleText).toBe('Foo Label +1 more');
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns first label name when `labels` prop has only one item present', () => {
+ expect(vm.dropdownToggleText).toBe('Foo Label');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element of type `button`', () => {
+ expect(vm.$el.nodeName).toBe('BUTTON');
+ });
+
+ it('renders component container element with required data attributes', () => {
+ expect(vm.$el.dataset.abilityName).toBe(vm.abilityName);
+ expect(vm.$el.dataset.fieldName).toBe(vm.fieldName);
+ expect(vm.$el.dataset.issueUpdate).toBe(vm.updatePath);
+ expect(vm.$el.dataset.labels).toBe(vm.labelsPath);
+ expect(vm.$el.dataset.namespacePath).toBe(vm.namespace);
+ expect(vm.$el.dataset.showAny).not.toBeDefined();
+ });
+
+ it('renders dropdown toggle text element', () => {
+ const dropdownToggleTextEl = vm.$el.querySelector('.dropdown-toggle-text');
+ expect(dropdownToggleTextEl).not.toBeNull();
+ expect(dropdownToggleTextEl.innerText.trim()).toBe('Foo Label');
+ });
+
+ it('renders dropdown button icon', () => {
+ const dropdownIconEl = vm.$el.querySelector('i.fa');
+ expect(dropdownIconEl).not.toBeNull();
+ expect(dropdownIconEl.classList.contains('fa-chevron-down')).toBe(true);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
new file mode 100644
index 00000000000..f07aefb2f87
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -0,0 +1,84 @@
+import Vue from 'vue';
+
+import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
+
+import { mockSuggestedColors } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(dropdownCreateLabelComponent);
+
+ return mountComponent(Component);
+};
+
+describe('DropdownCreateLabelComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ gon.suggested_label_colors = mockSuggestedColors;
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('created', () => {
+ it('initializes `suggestedColors` prop on component from `gon.suggested_color_labels` object', () => {
+ expect(vm.suggestedColors.length).toBe(mockSuggestedColors.length);
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `dropdown-page-two dropdown-new-label`', () => {
+ expect(vm.$el.classList.contains('dropdown-page-two', 'dropdown-new-label')).toBe(true);
+ });
+
+ it('renders `Go back` button on component header', () => {
+ const backButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-back');
+ expect(backButtonEl).not.toBe(null);
+ expect(backButtonEl.querySelector('.fa-arrow-left')).not.toBe(null);
+ });
+
+ it('renders component header element', () => {
+ const headerEl = vm.$el.querySelector('.dropdown-title');
+ expect(headerEl.innerText.trim()).toContain('Create new label');
+ });
+
+ it('renders `Close` button on component header', () => {
+ const closeButtonEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close');
+ expect(closeButtonEl).not.toBe(null);
+ expect(closeButtonEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBe(null);
+ });
+
+ it('renders `Name new label` input element', () => {
+ expect(vm.$el.querySelector('.dropdown-labels-error.js-label-error')).not.toBe(null);
+ expect(vm.$el.querySelector('input#new_label_name.default-dropdown-input')).not.toBe(null);
+ });
+
+ it('renders suggested colors list elements', () => {
+ const colorsListContainerEl = vm.$el.querySelector('.suggest-colors.suggest-colors-dropdown');
+ expect(colorsListContainerEl).not.toBe(null);
+ expect(colorsListContainerEl.querySelectorAll('a').length).toBe(mockSuggestedColors.length);
+
+ const colorItemEl = colorsListContainerEl.querySelectorAll('a')[0];
+ expect(colorItemEl.dataset.color).toBe(vm.suggestedColors[0]);
+ expect(colorItemEl.getAttribute('style')).toBe('background-color: rgb(0, 51, 204);');
+ });
+
+ it('renders color input element', () => {
+ expect(vm.$el.querySelector('.dropdown-label-color-input')).not.toBe(null);
+ expect(vm.$el.querySelector('.dropdown-label-color-preview.js-dropdown-label-color-preview')).not.toBe(null);
+ expect(vm.$el.querySelector('input#new_label_color.default-dropdown-input')).not.toBe(null);
+ });
+
+ it('renders component action buttons', () => {
+ const createBtnEl = vm.$el.querySelector('button.js-new-label-btn');
+ const cancelBtnEl = vm.$el.querySelector('button.js-cancel-label-btn');
+ expect(createBtnEl).not.toBe(null);
+ expect(createBtnEl.innerText.trim()).toBe('Create');
+ expect(cancelBtnEl.innerText.trim()).toBe('Cancel');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
new file mode 100644
index 00000000000..809e0327b1c
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+
+import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
+
+import { mockConfig } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (labelsWebUrl = mockConfig.labelsWebUrl) => {
+ const Component = Vue.extend(dropdownFooterComponent);
+
+ return mountComponent(Component, {
+ labelsWebUrl,
+ });
+};
+
+describe('DropdownFooterComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders `Create new label` link element', () => {
+ const createLabelEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-toggle-page');
+ expect(createLabelEl).not.toBeNull();
+ expect(createLabelEl.innerText.trim()).toBe('Create new label');
+ });
+
+ it('renders `Manage labels` link element', () => {
+ const manageLabelsEl = vm.$el.querySelector('.dropdown-footer-list .dropdown-external-link');
+ expect(manageLabelsEl).not.toBeNull();
+ expect(manageLabelsEl.getAttribute('href')).toBe(vm.labelsWebUrl);
+ expect(manageLabelsEl.innerText.trim()).toBe('Manage labels');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
new file mode 100644
index 00000000000..325fa47c957
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_header_spec.js
@@ -0,0 +1,36 @@
+import Vue from 'vue';
+
+import dropdownHeaderComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_header.vue';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(dropdownHeaderComponent);
+
+ return mountComponent(Component);
+};
+
+describe('DropdownHeaderComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders header text element', () => {
+ const headerEl = vm.$el.querySelector('.dropdown-title span');
+ expect(headerEl.innerText.trim()).toBe('Assign labels');
+ });
+
+ it('renders `Close` button element', () => {
+ const closeBtnEl = vm.$el.querySelector('.dropdown-title button.dropdown-title-button.dropdown-menu-close');
+ expect(closeBtnEl).not.toBeNull();
+ expect(closeBtnEl.querySelector('.fa-times.dropdown-menu-close-icon')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js
new file mode 100644
index 00000000000..703b87498c7
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_hidden_input_spec.js
@@ -0,0 +1,37 @@
+import Vue from 'vue';
+
+import dropdownHiddenInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_hidden_input.vue';
+
+import { mockLabels } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (name = 'label_id[]', label = mockLabels[0]) => {
+ const Component = Vue.extend(dropdownHiddenInputComponent);
+
+ return mountComponent(Component, {
+ name,
+ label,
+ });
+};
+
+describe('DropdownHiddenInputComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders input element of type `hidden`', () => {
+ expect(vm.$el.nodeName).toBe('INPUT');
+ expect(vm.$el.getAttribute('type')).toBe('hidden');
+ expect(vm.$el.getAttribute('name')).toBe(vm.name);
+ expect(vm.$el.getAttribute('value')).toBe(`${vm.label.id}`);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
new file mode 100644
index 00000000000..69e11d966c2
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_search_input_spec.js
@@ -0,0 +1,39 @@
+import Vue from 'vue';
+
+import dropdownSearchInputComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_search_input.vue';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = () => {
+ const Component = Vue.extend(dropdownSearchInputComponent);
+
+ return mountComponent(Component);
+};
+
+describe('DropdownSearchInputComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders input element with type `search`', () => {
+ const inputEl = vm.$el.querySelector('input.dropdown-input-field');
+ expect(inputEl).not.toBeNull();
+ expect(inputEl.getAttribute('type')).toBe('search');
+ });
+
+ it('renders search icon element', () => {
+ expect(vm.$el.querySelector('.fa-search.dropdown-input-search')).not.toBeNull();
+ });
+
+ it('renders clear search icon element', () => {
+ expect(vm.$el.querySelector('.fa-times.dropdown-input-clear.js-dropdown-input-clear')).not.toBeNull();
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
new file mode 100644
index 00000000000..c3580933072
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_title_spec.js
@@ -0,0 +1,42 @@
+import Vue from 'vue';
+
+import dropdownTitleComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_title.vue';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (canEdit = true) => {
+ const Component = Vue.extend(dropdownTitleComponent);
+
+ return mountComponent(Component, {
+ canEdit,
+ });
+};
+
+describe('DropdownTitleComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('template', () => {
+ it('renders title text', () => {
+ expect(vm.$el.classList.contains('title', 'hide-collapsed')).toBe(true);
+ expect(vm.$el.innerText.trim()).toContain('Labels');
+ });
+
+ it('renders spinner icon element', () => {
+ expect(vm.$el.querySelector('.fa-spinner.fa-spin.block-loading')).not.toBeNull();
+ });
+
+ it('renders `Edit` button element', () => {
+ const editBtnEl = vm.$el.querySelector('button.edit-link.js-sidebar-dropdown-toggle');
+ expect(editBtnEl).not.toBeNull();
+ expect(editBtnEl.innerText.trim()).toBe('Edit');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
new file mode 100644
index 00000000000..93b42795bea
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -0,0 +1,74 @@
+import Vue from 'vue';
+
+import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
+
+import { mockLabels } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (labels = mockLabels) => {
+ const Component = Vue.extend(dropdownValueCollapsedComponent);
+
+ return mountComponent(Component, {
+ labels,
+ });
+};
+
+describe('DropdownValueCollapsedComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('labelsList', () => {
+ it('returns empty text when `labels` prop is empty array', () => {
+ const vmEmptyLabels = createComponent([]);
+ expect(vmEmptyLabels.labelsList).toBe('');
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns labels names separated by coma when `labels` prop has more than one item', () => {
+ const vmMoreLabels = createComponent(mockLabels.concat(mockLabels));
+ expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label');
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns labels names separated by coma with remaining labels count and `and more` phrase when `labels` prop has more than five items', () => {
+ const mockMoreLabels = Object.assign([], mockLabels);
+ for (let i = 0; i < 6; i += 1) {
+ mockMoreLabels.unshift(mockLabels[0]);
+ }
+
+ const vmMoreLabels = createComponent(mockMoreLabels);
+ expect(vmMoreLabels.labelsList).toBe('Foo Label, Foo Label, Foo Label, Foo Label, Foo Label, and 2 more');
+ vmMoreLabels.$destroy();
+ });
+
+ it('returns first label name when `labels` prop has only one item present', () => {
+ expect(vm.labelsList).toBe('Foo Label');
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with tooltip`', () => {
+ expect(vm.$el.dataset.placement).toBe('left');
+ expect(vm.$el.dataset.container).toBe('body');
+ expect(vm.$el.dataset.originalTitle).toBe(vm.labelsList);
+ });
+
+ it('renders tags icon element', () => {
+ expect(vm.$el.querySelector('.fa-tags')).not.toBeNull();
+ });
+
+ it('renders labels count', () => {
+ expect(vm.$el.querySelector('span').innerText.trim()).toBe(`${vm.labels.length}`);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
new file mode 100644
index 00000000000..66e0957b431
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -0,0 +1,94 @@
+import Vue from 'vue';
+
+import dropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
+
+import { mockConfig, mockLabels } from './mock_data';
+
+import mountComponent from '../../../../helpers/vue_mount_component_helper';
+
+const createComponent = (
+ labels = mockLabels,
+ labelFilterBasePath = mockConfig.labelFilterBasePath,
+) => {
+ const Component = Vue.extend(dropdownValueComponent);
+
+ return mountComponent(Component, {
+ labels,
+ labelFilterBasePath,
+ });
+};
+
+describe('DropdownValueComponent', () => {
+ let vm;
+
+ beforeEach(() => {
+ vm = createComponent();
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ describe('computed', () => {
+ describe('isEmpty', () => {
+ it('returns true if `labels` prop is empty', () => {
+ const vmEmptyLabels = createComponent([]);
+ expect(vmEmptyLabels.isEmpty).toBe(true);
+ vmEmptyLabels.$destroy();
+ });
+
+ it('returns false if `labels` prop is empty', () => {
+ expect(vm.isEmpty).toBe(false);
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('labelFilterUrl', () => {
+ it('returns URL string starting with labelFilterBasePath and encoded label.title', () => {
+ expect(vm.labelFilterUrl({
+ title: 'Foo bar',
+ })).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20bar');
+ });
+ });
+
+ describe('labelStyle', () => {
+ it('returns object with `color` & `backgroundColor` properties from label.textColor & label.color', () => {
+ const label = {
+ textColor: '#FFFFFF',
+ color: '#BADA55',
+ };
+ const styleObj = vm.labelStyle(label);
+
+ expect(styleObj.color).toBe(label.textColor);
+ expect(styleObj.backgroundColor).toBe(label.color);
+ });
+ });
+ });
+
+ describe('template', () => {
+ it('renders component container element with classes `hide-collapsed value issuable-show-labels`', () => {
+ expect(vm.$el.classList.contains('hide-collapsed', 'value', 'issuable-show-labels')).toBe(true);
+ });
+
+ it('render slot content inside component when `labels` prop is empty', () => {
+ const vmEmptyLabels = createComponent([]);
+ expect(vmEmptyLabels.$el.querySelector('.text-secondary').innerText.trim()).toBe(mockConfig.emptyValueText);
+ vmEmptyLabels.$destroy();
+ });
+
+ it('renders label element with filter URL', () => {
+ expect(vm.$el.querySelector('a').getAttribute('href')).toBe('/gitlab-org/my-project/issues?label_name[]=Foo%20Label');
+ });
+
+ it('renders label element with tooltip and styles based on label details', () => {
+ const labelEl = vm.$el.querySelector('a span.label.color-label');
+ expect(labelEl).not.toBeNull();
+ expect(labelEl.dataset.placement).toBe('bottom');
+ expect(labelEl.dataset.container).toBe('body');
+ expect(labelEl.dataset.originalTitle).toBe(mockLabels[0].description);
+ expect(labelEl.getAttribute('style')).toBe('background-color: rgb(186, 218, 85);');
+ expect(labelEl.innerText.trim()).toBe(mockLabels[0].title);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
new file mode 100644
index 00000000000..e9008c29b22
--- /dev/null
+++ b/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
@@ -0,0 +1,49 @@
+export const mockLabels = [
+ {
+ id: 26,
+ title: 'Foo Label',
+ description: 'Foobar',
+ color: '#BADA55',
+ text_color: '#FFFFFF',
+ },
+];
+
+export const mockSuggestedColors = [
+ '#0033CC',
+ '#428BCA',
+ '#44AD8E',
+ '#A8D695',
+ '#5CB85C',
+ '#69D100',
+ '#004E00',
+ '#34495E',
+ '#7F8C8D',
+ '#A295D6',
+ '#5843AD',
+ '#8E44AD',
+ '#FFECDB',
+ '#AD4363',
+ '#D10069',
+ '#CC0033',
+ '#FF0000',
+ '#D9534F',
+ '#D1D100',
+ '#F0AD4E',
+ '#AD8D43',
+];
+
+export const mockConfig = {
+ showCreate: true,
+ abilityName: 'issue',
+ context: {
+ labels: mockLabels,
+ },
+ namespace: 'gitlab-org',
+ updatePath: '/gitlab-org/my-project/issue/1',
+ labelsPath: '/gitlab-org/my-project/labels.json',
+ labelsWebUrl: '/gitlab-org/my-project/labels',
+ labelFilterBasePath: '/gitlab-org/my-project/issues',
+ canEdit: true,
+ suggestedColors: mockSuggestedColors,
+ emptyValueText: 'None',
+};
diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb
index f7b1a61f4f8..a9b5ed1112a 100644
--- a/spec/lib/backup/repository_spec.rb
+++ b/spec/lib/backup/repository_spec.rb
@@ -28,6 +28,23 @@ describe Backup::Repository do
end
describe '#restore' do
+ subject { described_class.new }
+
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+ let(:temp_dirs) do
+ Gitlab.config.repositories.storages.map do |name, storage|
+ File.join(storage['path'], '..', 'repositories.old.' + timestamp.to_i.to_s)
+ end
+ end
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ after do
+ temp_dirs.each { |path| FileUtils.rm_rf(path) }
+ end
+
describe 'command failure' do
before do
allow(Gitlab::Popen).to receive(:popen).and_return(['error', 1])
@@ -35,7 +52,7 @@ describe Backup::Repository do
context 'hashed storage' do
it 'shows the appropriate error' do
- described_class.new.restore
+ subject.restore
expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} (#{project.disk_path}) - error")
end
@@ -45,7 +62,7 @@ describe Backup::Repository do
let!(:project) { create(:project, :legacy_storage) }
it 'shows the appropriate error' do
- described_class.new.restore
+ subject.restore
expect(progress).to have_received(:puts).with("Ignoring error on #{project.full_path} - error")
end
diff --git a/spec/lib/banzai/filter/autolink_filter_spec.rb b/spec/lib/banzai/filter/autolink_filter_spec.rb
index b7c2ff03125..b502daea418 100644
--- a/spec/lib/banzai/filter/autolink_filter_spec.rb
+++ b/spec/lib/banzai/filter/autolink_filter_spec.rb
@@ -4,6 +4,7 @@ describe Banzai::Filter::AutolinkFilter do
include FilterSpecHelper
let(:link) { 'http://about.gitlab.com/' }
+ let(:quotes) { ['"', "'"] }
it 'does nothing when :autolink is false' do
exp = act = link
@@ -15,17 +16,7 @@ describe Banzai::Filter::AutolinkFilter do
expect(filter(act).to_html).to eq exp
end
- context 'when the input contains no links' do
- it 'does not parse_html back the rinku returned value' do
- act = HTML::Pipeline.parse('<p>This text contains no links to autolink</p>')
-
- expect_any_instance_of(described_class).not_to receive(:parse_html)
-
- filter(act).to_html
- end
- end
-
- context 'Rinku schemes' do
+ context 'Various schemes' do
it 'autolinks http' do
doc = filter("See #{link}")
expect(doc.at_css('a').text).to eq link
@@ -56,32 +47,26 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a')['href']).to eq link
end
- it 'accepts link_attr options' do
- doc = filter("See #{link}", link_attr: { class: 'custom' })
+ it 'autolinks multiple URLs' do
+ link1 = 'http://localhost:3000/'
+ link2 = 'http://google.com/'
- expect(doc.at_css('a')['class']).to eq 'custom'
- end
+ doc = filter("See #{link1} and #{link2}")
- described_class::IGNORE_PARENTS.each do |elem|
- it "ignores valid links contained inside '#{elem}' element" do
- exp = act = "<#{elem}>See #{link}</#{elem}>"
- expect(filter(act).to_html).to eq exp
- end
- end
+ found_links = doc.css('a')
- context 'when the input contains link' do
- it 'does parse_html back the rinku returned value' do
- act = HTML::Pipeline.parse("<p>See #{link}</p>")
+ expect(found_links.size).to eq(2)
+ expect(found_links[0].text).to eq(link1)
+ expect(found_links[0]['href']).to eq(link1)
+ expect(found_links[1].text).to eq(link2)
+ expect(found_links[1]['href']).to eq(link2)
+ end
- expect_any_instance_of(described_class).to receive(:parse_html).at_least(:once).and_call_original
+ it 'accepts link_attr options' do
+ doc = filter("See #{link}", link_attr: { class: 'custom' })
- filter(act).to_html
- end
+ expect(doc.at_css('a')['class']).to eq 'custom'
end
- end
-
- context 'other schemes' do
- let(:link) { 'foo://bar.baz/' }
it 'autolinks smb' do
link = 'smb:///Volumes/shared/foo.pdf'
@@ -91,6 +76,21 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a')['href']).to eq link
end
+ it 'autolinks multiple occurences of smb' do
+ link1 = 'smb:///Volumes/shared/foo.pdf'
+ link2 = 'smb:///Volumes/shared/bar.pdf'
+
+ doc = filter("See #{link1} and #{link2}")
+
+ found_links = doc.css('a')
+
+ expect(found_links.size).to eq(2)
+ expect(found_links[0].text).to eq(link1)
+ expect(found_links[0]['href']).to eq(link1)
+ expect(found_links[1].text).to eq(link2)
+ expect(found_links[1]['href']).to eq(link2)
+ end
+
it 'autolinks irc' do
link = 'irc://irc.freenode.net/git'
doc = filter("See #{link}")
@@ -132,6 +132,45 @@ describe Banzai::Filter::AutolinkFilter do
expect(doc.at_css('a').text).to eq link
end
+ it 'includes trailing punctuation when part of a balanced pair' do
+ described_class::PUNCTUATION_PAIRS.each do |close, open|
+ next if open.in?(quotes)
+
+ balanced_link = "#{link}#{open}abc#{close}"
+ balanced_actual = filter("See #{balanced_link}...")
+ unbalanced_link = "#{link}#{close}"
+ unbalanced_actual = filter("See #{unbalanced_link}...")
+
+ expect(balanced_actual.at_css('a').text).to eq(balanced_link)
+ expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+ expect(unbalanced_actual.at_css('a').text).to eq(link)
+ expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+ end
+ end
+
+ it 'removes trailing quotes' do
+ quotes.each do |quote|
+ balanced_link = "#{link}#{quote}abc#{quote}"
+ balanced_actual = filter("See #{balanced_link}...")
+ unbalanced_link = "#{link}#{quote}"
+ unbalanced_actual = filter("See #{unbalanced_link}...")
+
+ expect(balanced_actual.at_css('a').text).to eq(balanced_link[0...-1])
+ expect(unescape(balanced_actual.to_html)).to eq(Rinku.auto_link("See #{balanced_link}..."))
+ expect(unbalanced_actual.at_css('a').text).to eq(link)
+ expect(unescape(unbalanced_actual.to_html)).to eq(Rinku.auto_link("See #{unbalanced_link}..."))
+ end
+ end
+
+ it 'removes one closing punctuation mark when the punctuation in the link is unbalanced' do
+ complicated_link = "(#{link}(a'b[c'd]))'"
+ expected_complicated_link = %Q{(<a href="#{link}(a'b[c'd]))">#{link}(a'b[c'd]))</a>'}
+ actual = unescape(filter(complicated_link).to_html)
+
+ expect(actual).to eq(Rinku.auto_link(complicated_link))
+ expect(actual).to eq(expected_complicated_link)
+ end
+
it 'does not include trailing HTML entities' do
doc = filter("See &lt;&lt;&lt;#{link}&gt;&gt;&gt;")
@@ -151,4 +190,29 @@ describe Banzai::Filter::AutolinkFilter do
end
end
end
+
+ context 'when the link is inside a tag' do
+ %w[http rdar].each do |protocol|
+ it "renders text after the link correctly for #{protocol}" do
+ doc = filter(ERB::Util.html_escape_once("<#{protocol}://link><another>"))
+
+ expect(doc.children.last.text).to include('<another>')
+ end
+ end
+ end
+
+ # Rinku does not escape these characters in HTML attributes, but content_tag
+ # does. We don't care about that difference for these specs, though.
+ def unescape(html)
+ %w([ ] { }).each do |cgi_escape|
+ html.sub!(CGI.escape(cgi_escape), cgi_escape)
+ end
+
+ quotes.each do |html_escape|
+ html.sub!(CGI.escape_html(html_escape), html_escape)
+ html.sub!(CGI.escape(html_escape), CGI.escape_html(html_escape))
+ end
+
+ html
+ end
end
diff --git a/spec/lib/banzai/filter/label_reference_filter_spec.rb b/spec/lib/banzai/filter/label_reference_filter_spec.rb
index 862b1fe3fd3..0c524a1551f 100644
--- a/spec/lib/banzai/filter/label_reference_filter_spec.rb
+++ b/spec/lib/banzai/filter/label_reference_filter_spec.rb
@@ -381,11 +381,11 @@ describe Banzai::Filter::LabelReferenceFilter do
end
it 'has valid link text' do
- expect(result.css('a').first.text).to eq "#{label.name} in #{project2.name_with_namespace}"
+ expect(result.css('a').first.text).to eq "#{label.name} in #{project2.full_name}"
end
it 'has valid text' do
- expect(result.text).to eq "See #{label.name} in #{project2.name_with_namespace}"
+ expect(result.text).to eq "See #{label.name} in #{project2.full_name}"
end
it 'ignores invalid IDs on the referenced label' do
@@ -481,12 +481,12 @@ describe Banzai::Filter::LabelReferenceFilter do
it 'has valid link text' do
expect(result.css('a').first.text)
- .to eq "#{group_label.name} in #{another_project.name_with_namespace}"
+ .to eq "#{group_label.name} in #{another_project.full_name}"
end
it 'has valid text' do
expect(result.text)
- .to eq "See #{group_label.name} in #{another_project.name_with_namespace}"
+ .to eq "See #{group_label.name} in #{another_project.full_name}"
end
it 'ignores invalid IDs on the referenced label' do
diff --git a/spec/lib/gitlab/checks/lfs_integrity_spec.rb b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
index 17756621221..7201e4f7bf6 100644
--- a/spec/lib/gitlab/checks/lfs_integrity_spec.rb
+++ b/spec/lib/gitlab/checks/lfs_integrity_spec.rb
@@ -2,23 +2,25 @@ require 'spec_helper'
describe Gitlab::Checks::LfsIntegrity do
include ProjectForksHelper
+
let(:project) { create(:project, :repository) }
- let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
+ let(:repository) { project.repository }
+ let(:newrev) do
+ operations = BareRepoOperations.new(repository.path)
+
+ # Create a commit not pointed at by any ref to emulate being in the
+ # pre-receive hook so that `--not --all` returns some objects
+ operations.commit_tree('8856a329dd38ca86dfb9ce5aa58a16d88cc119bd', "New LFS objects")
+ end
subject { described_class.new(project, newrev) }
describe '#objects_missing?' do
- let(:blob_object) { project.repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
-
- before do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects) do |&lazy_block|
- lazy_block.call([blob_object.id])
- end
- end
+ let(:blob_object) { repository.blob_at_branch('lfs', 'files/lfs/lfs_object.iso') }
context 'with LFS not enabled' do
it 'skips integrity check' do
- expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+ expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
subject.objects_missing?
end
@@ -33,7 +35,7 @@ describe Gitlab::Checks::LfsIntegrity do
let(:newrev) { nil }
it 'skips integrity check' do
- expect_any_instance_of(Gitlab::Git::RevList).not_to receive(:new_objects)
+ expect_any_instance_of(Gitlab::Git::LfsChanges).not_to receive(:new_pointers)
expect(subject.objects_missing?).to be_falsey
end
diff --git a/spec/lib/gitlab/ci/trace_spec.rb b/spec/lib/gitlab/ci/trace_spec.rb
index 91c9625ba06..1c73043cfbd 100644
--- a/spec/lib/gitlab/ci/trace_spec.rb
+++ b/spec/lib/gitlab/ci/trace_spec.rb
@@ -399,4 +399,138 @@ describe Gitlab::Ci::Trace do
end
end
end
+
+ describe '#archive!' do
+ subject { trace.archive! }
+
+ shared_examples 'archive trace file' do
+ it do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(1)
+
+ build.reload
+ expect(build.trace.exist?).to be_truthy
+ expect(build.job_artifacts_trace.file.exists?).to be_truthy
+ expect(build.job_artifacts_trace.file.filename).to eq('job.log')
+ expect(File.exist?(src_path)).to be_falsy
+ expect(src_checksum)
+ .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).digest)
+ end
+ end
+
+ shared_examples 'source trace file stays intact' do |error:|
+ it do
+ expect { subject }.to raise_error(error)
+
+ build.reload
+ expect(build.trace.exist?).to be_truthy
+ expect(build.job_artifacts_trace).to be_nil
+ expect(File.exist?(src_path)).to be_truthy
+ end
+ end
+
+ shared_examples 'archive trace in database' do
+ it do
+ expect { subject }.to change { Ci::JobArtifact.count }.by(1)
+
+ build.reload
+ expect(build.trace.exist?).to be_truthy
+ expect(build.job_artifacts_trace.file.exists?).to be_truthy
+ expect(build.job_artifacts_trace.file.filename).to eq('job.log')
+ expect(build.old_trace).to be_nil
+ expect(src_checksum)
+ .to eq(Digest::SHA256.file(build.job_artifacts_trace.file.path).digest)
+ end
+ end
+
+ shared_examples 'source trace in database stays intact' do |error:|
+ it do
+ expect { subject }.to raise_error(error)
+
+ build.reload
+ expect(build.trace.exist?).to be_truthy
+ expect(build.job_artifacts_trace).to be_nil
+ expect(build.old_trace).to eq(trace_content)
+ end
+ end
+
+ context 'when job does not have trace artifact' do
+ context 'when trace file stored in default path' do
+ let!(:build) { create(:ci_build, :success, :trace_live) }
+ let!(:src_path) { trace.read { |s| return s.path } }
+ let!(:src_checksum) { Digest::SHA256.file(src_path).digest }
+
+ it_behaves_like 'archive trace file'
+
+ context 'when failed to create clone file' do
+ before do
+ allow(IO).to receive(:copy_stream).and_return(0)
+ end
+
+ it_behaves_like 'source trace file stays intact', error: Gitlab::Ci::Trace::ArchiveError
+ end
+
+ context 'when failed to create job artifact record' do
+ before do
+ allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
+ allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
+ .and_return(%w[Error Error])
+ end
+
+ it_behaves_like 'source trace file stays intact', error: ActiveRecord::RecordInvalid
+ end
+ end
+
+ context 'when trace is stored in database' do
+ let(:build) { create(:ci_build, :success) }
+ let(:trace_content) { 'Sample trace' }
+ let!(:src_checksum) { Digest::SHA256.digest(trace_content) }
+
+ before do
+ build.update_column(:trace, trace_content)
+ end
+
+ it_behaves_like 'archive trace in database'
+
+ context 'when failed to create clone file' do
+ before do
+ allow(IO).to receive(:copy_stream).and_return(0)
+ end
+
+ it_behaves_like 'source trace in database stays intact', error: Gitlab::Ci::Trace::ArchiveError
+ end
+
+ context 'when failed to create job artifact record' do
+ before do
+ allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
+ allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
+ .and_return(%w[Error Error])
+ end
+
+ it_behaves_like 'source trace in database stays intact', error: ActiveRecord::RecordInvalid
+ end
+ end
+ end
+
+ context 'when job has trace artifact' do
+ before do
+ create(:ci_job_artifact, :trace, job: build)
+ end
+
+ it 'does not archive' do
+ expect_any_instance_of(described_class).not_to receive(:archive_stream!)
+ expect { subject }.to raise_error('Already archived')
+ expect(build.job_artifacts_trace.file.exists?).to be_truthy
+ end
+ end
+
+ context 'when job is not finished yet' do
+ let!(:build) { create(:ci_build, :running, :trace_live) }
+
+ it 'does not archive' do
+ expect_any_instance_of(described_class).not_to receive(:archive_stream!)
+ expect { subject }.to raise_error('Job is not finished yet')
+ expect(build.trace.exist?).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/contributions_calendar_spec.rb b/spec/lib/gitlab/contributions_calendar_spec.rb
index 49a179ba875..167876ca158 100644
--- a/spec/lib/gitlab/contributions_calendar_spec.rb
+++ b/spec/lib/gitlab/contributions_calendar_spec.rb
@@ -11,7 +11,7 @@ describe Gitlab::ContributionsCalendar do
end
let(:public_project) do
- create(:project, :public) do |project|
+ create(:project, :public, :repository) do |project|
create(:project_member, user: contributor, project: project)
end
end
@@ -40,13 +40,13 @@ describe Gitlab::ContributionsCalendar do
described_class.new(contributor, current_user)
end
- def create_event(project, day, hour = 0)
+ def create_event(project, day, hour = 0, action = Event::CREATED, target_symbol = :issue)
@targets ||= {}
- @targets[project] ||= create(:issue, project: project, author: contributor)
+ @targets[project] ||= create(target_symbol, project: project, author: contributor)
Event.create!(
project: project,
- action: Event::CREATED,
+ action: action,
target: @targets[project],
author: contributor,
created_at: DateTime.new(day.year, day.month, day.day, hour)
@@ -71,6 +71,12 @@ describe Gitlab::ContributionsCalendar do
expect(calendar(contributor).activity_dates[today]).to eq(2)
end
+ it "counts the diff notes on merge request" do
+ create_event(public_project, today, 0, Event::COMMENTED, :diff_note_on_merge_request)
+
+ expect(calendar(contributor).activity_dates[today]).to eq(1)
+ end
+
context "when events fall under different dates depending on the time zone" do
before do
create_event(public_project, today, 1)
diff --git a/spec/lib/gitlab/data_builder/build_spec.rb b/spec/lib/gitlab/data_builder/build_spec.rb
index 91c43f2bdc0..ee91decafad 100644
--- a/spec/lib/gitlab/data_builder/build_spec.rb
+++ b/spec/lib/gitlab/data_builder/build_spec.rb
@@ -16,7 +16,7 @@ describe Gitlab::DataBuilder::Build do
it { expect(data[:build_status]).to eq(build.status) }
it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
- it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
+ it { expect(data[:project_name]).to eq(build.project.full_name) }
context 'commit author_url' do
context 'when no commit present' do
diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
index 031efcf1291..53899e00b53 100644
--- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
+++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb
@@ -55,8 +55,8 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError)
end
- context 'because the note was commands only' do
- let!(:email_raw) { fixture_file("emails/commands_only_reply.eml") }
+ context 'because the note was update commands only' do
+ let!(:email_raw) { fixture_file("emails/update_commands_only_reply.eml") }
context 'and current user cannot update noteable' do
it 'raises a CommandsOnlyNoteError' do
@@ -70,13 +70,10 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
end
it 'does not raise an error' do
- expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
-
# One system note is created for the 'close' event
expect { receiver.execute }.to change { noteable.notes.count }.by(1)
expect(noteable.reload).to be_closed
- expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
end
end
end
@@ -85,15 +82,13 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
context 'when the note contains quick actions' do
let!(:email_raw) { fixture_file("emails/commands_in_reply.eml") }
- context 'and current user cannot update noteable' do
- it 'post a note and does not update the noteable' do
- expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
-
- # One system note is created for the new note
- expect { receiver.execute }.to change { noteable.notes.count }.by(1)
+ context 'and current user cannot update the noteable' do
+ it 'only executes the commands that the user can perform' do
+ expect { receiver.execute }
+ .to change { noteable.notes.user.count }.by(1)
+ .and change { user.todos_pending_count }.from(0).to(1)
expect(noteable.reload).to be_open
- expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
end
end
@@ -102,14 +97,14 @@ describe Gitlab::Email::Handler::CreateNoteHandler do
project.add_developer(user)
end
- it 'post a note and updates the noteable' do
+ it 'posts a note and updates the noteable' do
expect(TodoService.new.todo_exist?(noteable, user)).to be_falsy
- # One system note is created for the new note, one for the 'close' event
- expect { receiver.execute }.to change { noteable.notes.count }.by(2)
+ expect { receiver.execute }
+ .to change { noteable.notes.user.count }.by(1)
+ .and change { user.todos_pending_count }.from(0).to(1)
expect(noteable.reload).to be_closed
- expect(TodoService.new.todo_exist?(noteable, user)).to be_truthy
end
end
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index a6341cd509b..67d898e787e 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -500,4 +500,33 @@ describe Gitlab::Git::Blob, seed_helper: true do
end
end
end
+
+ describe '#load_all_data!' do
+ let(:full_data) { 'abcd' }
+ let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: 'abc') }
+
+ subject { blob.load_all_data!(repository) }
+
+ it 'loads missing data' do
+ expect(Gitlab::GitalyClient).to receive(:migrate)
+ .with(:git_blob_load_all_data).and_return(full_data)
+
+ subject
+
+ expect(blob.data).to eq(full_data)
+ end
+
+ context 'with a fully loaded blob' do
+ let(:blob) { Gitlab::Git::Blob.new(name: 'test', size: 4, data: full_data) }
+
+ it "doesn't perform any loading" do
+ expect(Gitlab::GitalyClient).not_to receive(:migrate)
+ .with(:git_blob_load_all_data)
+
+ subject
+
+ expect(blob.data).to eq(full_data)
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 708870060e7..a19155ed5b0 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -59,5 +59,69 @@ describe Gitlab::Git::Branch, seed_helper: true do
it { expect(branch.dereferenced_target.sha).to eq(SeedRepo::LastCommit::ID) }
end
+ context 'with active, stale and future branches' do
+ let(:repository) do
+ Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
+ end
+
+ let(:user) { create(:user) }
+ let(:committer) do
+ Gitlab::Git.committer_hash(email: user.email, name: user.name)
+ end
+ let(:params) do
+ parents = [repository.rugged.head.target]
+ tree = parents.first.tree
+
+ {
+ message: 'commit message',
+ author: committer,
+ committer: committer,
+ tree: tree,
+ parents: parents
+ }
+ end
+ let(:stale_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago - 5.days) { create_commit } }
+ let(:active_sha) { Timecop.freeze(Gitlab::Git::Branch::STALE_BRANCH_THRESHOLD.ago + 5.days) { create_commit } }
+ let(:future_sha) { Timecop.freeze(100.days.since) { create_commit } }
+
+ before do
+ repository.create_branch('stale-1', stale_sha)
+ repository.create_branch('active-1', active_sha)
+ repository.create_branch('future-1', future_sha)
+ end
+
+ after do
+ ensure_seeds
+ end
+
+ describe 'examine if the branch is active or stale' do
+ let(:stale_branch) { repository.find_branch('stale-1') }
+ let(:active_branch) { repository.find_branch('active-1') }
+ let(:future_branch) { repository.find_branch('future-1') }
+
+ describe '#active?' do
+ it { expect(stale_branch.active?).to be_falsey }
+ it { expect(active_branch.active?).to be_truthy }
+ it { expect(future_branch.active?).to be_truthy }
+ end
+
+ describe '#stale?' do
+ it { expect(stale_branch.stale?).to be_truthy }
+ it { expect(active_branch.stale?).to be_falsey }
+ it { expect(future_branch.stale?).to be_falsey }
+ end
+
+ describe '#state' do
+ it { expect(stale_branch.state).to eq(:stale) }
+ it { expect(active_branch.state).to eq(:active) }
+ it { expect(future_branch.state).to eq(:active) }
+ end
+ end
+ end
+
it { expect(repository.branches.size).to eq(SeedRepo::Repo::BRANCHES.size) }
+
+ def create_commit
+ repository.create_commit(params.merge(committer: committer.merge(time: Time.now)))
+ end
end
diff --git a/spec/lib/gitlab/git/lfs_changes_spec.rb b/spec/lib/gitlab/git/lfs_changes_spec.rb
index c9007d7d456..d0dd8c6303f 100644
--- a/spec/lib/gitlab/git/lfs_changes_spec.rb
+++ b/spec/lib/gitlab/git/lfs_changes_spec.rb
@@ -7,34 +7,36 @@ describe Gitlab::Git::LfsChanges do
subject { described_class.new(project.repository, newrev) }
- describe 'new_pointers' do
- before do
- allow_any_instance_of(Gitlab::Git::RevList).to receive(:new_objects).and_yield([blob_object_id])
+ describe '#new_pointers' do
+ shared_examples 'new pointers' do
+ it 'filters new objects to find lfs pointers' do
+ expect(subject.new_pointers(not_in: []).first.id).to eq(blob_object_id)
+ end
+
+ it 'limits new_objects using object_limit' do
+ expect(subject.new_pointers(object_limit: 1)).to eq([])
+ end
end
- it 'uses rev-list to find new objects' do
- rev_list = double
- allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
-
- expect(rev_list).to receive(:new_objects).and_return([])
-
- subject.new_pointers
+ context 'with gitaly enabled' do
+ it_behaves_like 'new pointers'
end
- it 'filters new objects to find lfs pointers' do
- expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [blob_object_id])
+ context 'with gitaly disabled', :skip_gitaly_mock do
+ it_behaves_like 'new pointers'
- subject.new_pointers(object_limit: 1)
- end
+ it 'uses rev-list to find new objects' do
+ rev_list = double
+ allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
- it 'limits new_objects using object_limit' do
- expect(Gitlab::Git::Blob).to receive(:batch_lfs_pointers).with(project.repository, [])
+ expect(rev_list).to receive(:new_objects).and_return([])
- subject.new_pointers(object_limit: 0)
+ subject.new_pointers
+ end
end
end
- describe 'all_pointers' do
+ describe '#all_pointers', :skip_gitaly_mock do
it 'uses rev-list to find all objects' do
rev_list = double
allow(Gitlab::Git::RevList).to receive(:new).and_return(rev_list)
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 25defb98b7c..52c9876cbb6 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -751,255 +751,263 @@ describe Gitlab::Git::Repository, seed_helper: true do
end
describe "#log" do
- let(:commit_with_old_name) do
- Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
- end
- let(:commit_with_new_name) do
- Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
- end
- let(:rename_commit) do
- Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
- end
-
- before(:context) do
- # Add new commits so that there's a renamed file in the commit history
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- @commit_with_old_name_id = new_commit_edit_old_file(repo)
- @rename_commit_id = new_commit_move_file(repo)
- @commit_with_new_name_id = new_commit_edit_new_file(repo)
- end
-
- after(:context) do
- # Erase our commits so other tests get the original repo
- repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
- repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
- end
-
- context "where 'follow' == true" do
- let(:options) { { ref: "master", follow: true } }
+ shared_examples 'repository log' do
+ let(:commit_with_old_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_old_name_id)
+ end
+ let(:commit_with_new_name) do
+ Gitlab::Git::Commit.decorate(repository, @commit_with_new_name_id)
+ end
+ let(:rename_commit) do
+ Gitlab::Git::Commit.decorate(repository, @rename_commit_id)
+ end
- context "and 'path' is a directory" do
- it "does not follow renames" do
- log_commits = repository.log(options.merge(path: "encoding"))
+ before(:context) do
+ # Add new commits so that there's a renamed file in the commit history
+ repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ @commit_with_old_name_id = new_commit_edit_old_file(repo)
+ @rename_commit_id = new_commit_move_file(repo)
+ @commit_with_new_name_id = new_commit_edit_new_file(repo)
+ end
- aggregate_failures do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ after(:context) do
+ # Erase our commits so other tests get the original repo
+ repo = Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '').rugged
+ repo.references.update("refs/heads/master", SeedRepo::LastCommit::ID)
end
- context "and 'path' is a file that matches the new filename" do
- context 'without offset' do
- it "follows renames" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
+ context "where 'follow' == true" do
+ let(:options) { { ref: "master", follow: true } }
+
+ context "and 'path' is a directory" do
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "encoding"))
aggregate_failures do
expect(log_commits).to include(commit_with_new_name)
expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).not_to include(commit_with_old_name)
end
end
end
- context 'with offset=1' do
- it "follows renames and skip the latest commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
+ context "and 'path' is a file that matches the new filename" do
+ context 'without offset' do
+ it "follows renames" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG"))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
end
end
- end
- context 'with offset=1', 'and limit=1' do
- it "follows renames, skip the latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
+ context 'with offset=1' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1))
- expect(log_commits).to contain_exactly(rename_commit)
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
- end
- context 'with offset=1', 'and limit=2' do
- it "follows renames, skip the latest commit and return only two commits" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
+ context 'with offset=1', 'and limit=1' do
+ it "follows renames, skip the latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 1))
- aggregate_failures do
- expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+ expect(log_commits).to contain_exactly(rename_commit)
end
end
- end
- context 'with offset=2' do
- it "follows renames and skip the latest commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
+ context 'with offset=1', 'and limit=2' do
+ it "follows renames, skip the latest commit and return only two commits" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 1, limit: 2))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).not_to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).to contain_exactly(rename_commit, commit_with_old_name)
+ end
end
end
- end
- context 'with offset=2', 'and limit=1' do
- it "follows renames, skip the two latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+ context 'with offset=2' do
+ it "follows renames and skip the latest commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2))
- expect(log_commits).to contain_exactly(commit_with_old_name)
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
+ end
+
+ context 'with offset=2', 'and limit=1' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 1))
+
+ expect(log_commits).to contain_exactly(commit_with_old_name)
+ end
+ end
+
+ context 'with offset=2', 'and limit=2' do
+ it "follows renames, skip the two latest commit and return only one commit" do
+ log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+
+ aggregate_failures do
+ expect(log_commits).not_to include(commit_with_new_name)
+ expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(commit_with_old_name)
+ end
+ end
end
end
- context 'with offset=2', 'and limit=2' do
- it "follows renames, skip the two latest commit and return only one commit" do
- log_commits = repository.log(options.merge(path: "encoding/CHANGELOG", offset: 2, limit: 2))
+ context "and 'path' is a file that matches the old filename" do
+ it "does not follow renames" do
+ log_commits = repository.log(options.merge(path: "CHANGELOG"))
aggregate_failures do
expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).not_to include(rename_commit)
+ expect(log_commits).to include(rename_commit)
expect(log_commits).to include(commit_with_old_name)
end
end
end
- end
- context "and 'path' is a file that matches the old filename" do
- it "does not follow renames" do
- log_commits = repository.log(options.merge(path: "CHANGELOG"))
+ context "unknown ref" do
+ it "returns an empty array" do
+ log_commits = repository.log(options.merge(ref: 'unknown'))
- aggregate_failures do
- expect(log_commits).not_to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).to eq([])
end
end
end
- context "unknown ref" do
- it "returns an empty array" do
- log_commits = repository.log(options.merge(ref: 'unknown'))
-
- expect(log_commits).to eq([])
- end
- end
- end
+ context "where 'follow' == false" do
+ options = { follow: false }
- context "where 'follow' == false" do
- options = { follow: false }
+ context "and 'path' is a directory" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "encoding"))
+ end
- context "and 'path' is a directory" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_old_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ context "and 'path' is a file that matches the new filename" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "encoding/CHANGELOG"))
+ end
- context "and 'path' is a file that matches the new filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "encoding/CHANGELOG"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_new_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_old_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_new_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_old_name)
- end
- end
+ context "and 'path' is a file that matches the old filename" do
+ let(:log_commits) do
+ repository.log(options.merge(path: "CHANGELOG"))
+ end
- context "and 'path' is a file that matches the old filename" do
- let(:log_commits) do
- repository.log(options.merge(path: "CHANGELOG"))
+ it "does not follow renames" do
+ expect(log_commits).to include(commit_with_old_name)
+ expect(log_commits).to include(rename_commit)
+ expect(log_commits).not_to include(commit_with_new_name)
+ end
end
- it "does not follow renames" do
- expect(log_commits).to include(commit_with_old_name)
- expect(log_commits).to include(rename_commit)
- expect(log_commits).not_to include(commit_with_new_name)
+ context "and 'path' includes a directory that used to be a file" do
+ let(:log_commits) do
+ repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
+ end
+
+ it "returns a list of commits" do
+ expect(log_commits.size).to eq(1)
+ end
end
end
- context "and 'path' includes a directory that used to be a file" do
- let(:log_commits) do
- repository.log(options.merge(ref: "refs/heads/fix-blob-path", path: "files/testdir/file.txt"))
- end
+ context "where provides 'after' timestamp" do
+ options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "returns a list of commits" do
- expect(log_commits.size).to eq(1)
+ it "should returns commits on or after that timestamp" do
+ commits = repository.log(options)
+
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.all? { |commit| commit.committed_date >= options[:after] }
+ end
end
end
- end
- context "where provides 'after' timestamp" do
- options = { after: Time.iso8601('2014-03-03T20:15:01+00:00') }
+ context "where provides 'before' timestamp" do
+ options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
- it "should returns commits on or after that timestamp" do
- commits = repository.log(options)
+ it "should returns commits on or before that timestamp" do
+ commits = repository.log(options)
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.committed_date >= options[:after] }
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.all? { |commit| commit.committed_date <= options[:before] }
+ end
end
end
- end
- context "where provides 'before' timestamp" do
- options = { before: Time.iso8601('2014-03-03T20:15:01+00:00') }
+ context 'when multiple paths are provided' do
+ let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
- it "should returns commits on or before that timestamp" do
- commits = repository.log(options)
-
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.all? { |commit| commit.committed_date <= options[:before] }
+ def commit_files(commit)
+ commit.rugged_diff_from_parent.deltas.flat_map do |delta|
+ [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+ end
end
- end
- end
- context 'when multiple paths are provided' do
- let(:options) { { ref: 'master', path: ['PROCESS.md', 'README.md'] } }
+ it 'only returns commits matching at least one path' do
+ commits = repository.log(options)
- def commit_files(commit)
- commit.rugged_diff_from_parent.deltas.flat_map do |delta|
- [delta.old_file[:path], delta.new_file[:path]].uniq.compact
+ expect(commits.size).to be > 0
+ expect(commits).to satisfy do |commits|
+ commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+ end
end
end
- it 'only returns commits matching at least one path' do
- commits = repository.log(options)
+ context 'limit validation' do
+ where(:limit) do
+ [0, nil, '', 'foo']
+ end
- expect(commits.size).to be > 0
- expect(commits).to satisfy do |commits|
- commits.none? { |commit| (commit_files(commit) & options[:path]).empty? }
+ with_them do
+ it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
end
end
- end
- context 'limit validation' do
- where(:limit) do
- [0, nil, '', 'foo']
- end
+ context 'with all' do
+ it 'returns a list of commits' do
+ commits = repository.log({ all: true, limit: 50 })
- with_them do
- it { expect { repository.log(limit: limit) }.to raise_error(ArgumentError) }
+ expect(commits.size).to eq(37)
+ end
end
end
- context 'with all' do
- let(:options) { { all: true, limit: 50 } }
-
- it 'returns a list of commits' do
- commits = repository.log(options)
+ context 'when Gitaly find_commits feature is enabled' do
+ it_behaves_like 'repository log'
+ end
- expect(commits.size).to eq(37)
- end
+ context 'when Gitaly find_commits feature is disabled', :disable_gitaly do
+ it_behaves_like 'repository log'
end
end
@@ -1136,14 +1144,6 @@ describe Gitlab::Git::Repository, seed_helper: true do
expect(repository.count_commits(options)).to eq(10)
end
end
- end
-
- context 'when Gitaly count_commits feature is enabled' do
- it_behaves_like 'extended commit counting'
- end
-
- context 'when Gitaly count_commits feature is disabled', :skip_gitaly_mock do
- it_behaves_like 'extended commit counting'
context "with all" do
it "returns the number of commits in the whole repository" do
@@ -1155,10 +1155,18 @@ describe Gitlab::Git::Repository, seed_helper: true do
context 'without all or ref being specified' do
it "raises an ArgumentError" do
- expect { repository.count_commits({}) }.to raise_error(ArgumentError, "Please specify a valid ref or set the 'all' attribute to true")
+ expect { repository.count_commits({}) }.to raise_error(ArgumentError)
end
end
end
+
+ context 'when Gitaly count_commits feature is enabled' do
+ it_behaves_like 'extended commit counting'
+ end
+
+ context 'when Gitaly count_commits feature is disabled', :disable_gitaly do
+ it_behaves_like 'extended commit counting'
+ end
end
describe '#autocrlf' do
diff --git a/spec/lib/gitlab/gitaly_client/blob_service_spec.rb b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
new file mode 100644
index 00000000000..a2770ef2fe4
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/blob_service_spec.rb
@@ -0,0 +1,60 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::BlobService do
+ let(:project) { create(:project, :repository) }
+ let(:storage_name) { project.repository_storage }
+ let(:relative_path) { project.disk_path + '.git' }
+ let(:repository) { project.repository }
+ let(:client) { described_class.new(repository) }
+
+ describe '#get_new_lfs_pointers' do
+ let(:revision) { 'master' }
+ let(:limit) { 5 }
+ let(:not_in) { ['branch-a', 'branch-b'] }
+ let(:expected_params) do
+ { revision: revision, limit: limit, not_in_refs: not_in, not_in_all: false }
+ end
+
+ subject { client.get_new_lfs_pointers(revision, limit, not_in) }
+
+ it 'sends a get_new_lfs_pointers message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_new_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+
+ context 'with not_in = :all' do
+ let(:not_in) { :all }
+ let(:expected_params) do
+ { revision: revision, limit: limit, not_in_refs: [], not_in_all: true }
+ end
+
+ it 'sends the correct message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_new_lfs_pointers)
+ .with(gitaly_request_with_params(expected_params), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+ end
+
+ describe '#get_all_lfs_pointers' do
+ let(:revision) { 'master' }
+
+ subject { client.get_all_lfs_pointers(revision) }
+
+ it 'sends a get_all_lfs_pointers message' do
+ expect_any_instance_of(Gitaly::BlobService::Stub)
+ .to receive(:get_all_lfs_pointers)
+ .with(gitaly_request_with_params(revision: revision), kind_of(Hash))
+ .and_return([])
+
+ subject
+ end
+ end
+end
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index 41a55027f4d..b20cc34dd5c 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -277,6 +277,7 @@ project:
- fork_network
- custom_attributes
- lfs_file_locks
+- project_badges
award_emoji:
- awardable
- user
@@ -293,3 +294,5 @@ issue_assignees:
- assignee
lfs_file_locks:
- user
+project_badges:
+- project
diff --git a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
index a93a921e459..4897d604bc1 100644
--- a/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_restorer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::AvatarRestorer do
include UploadHelpers
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:shared) { project.import_export_shared }
let(:project) { create(:project) }
before do
diff --git a/spec/lib/gitlab/import_export/avatar_saver_spec.rb b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
index 3fb5ddde8b5..f40d4bc2d08 100644
--- a/spec/lib/gitlab/import_export/avatar_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/avatar_saver_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::ImportExport::AvatarSaver do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:shared) { project.import_export_shared }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:project_with_avatar) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) }
let(:project) { create(:project) }
diff --git a/spec/lib/gitlab/import_export/file_importer_spec.rb b/spec/lib/gitlab/import_export/file_importer_spec.rb
index 5cdc5138fda..58b9fb06cc5 100644
--- a/spec/lib/gitlab/import_export/file_importer_spec.rb
+++ b/spec/lib/gitlab/import_export/file_importer_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::ImportExport::FileImporter do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: 'test') }
+ let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
let(:export_path) { "#{Dir.tmpdir}/file_importer_spec" }
let(:valid_file) { "#{shared.export_path}/valid.json" }
let(:symlink_file) { "#{shared.export_path}/invalid.json" }
@@ -12,6 +12,7 @@ describe Gitlab::ImportExport::FileImporter do
stub_const('Gitlab::ImportExport::FileImporter::MAX_RETRIES', 0)
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
allow_any_instance_of(Gitlab::ImportExport::CommandLineUtil).to receive(:untar_zxf).and_return(true)
+ allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('test')
allow(SecureRandom).to receive(:hex).and_return('abcd')
setup_files
end
diff --git a/spec/lib/gitlab/import_export/fork_spec.rb b/spec/lib/gitlab/import_export/fork_spec.rb
index cfb15ee7e8b..17e06a6a83f 100644
--- a/spec/lib/gitlab/import_export/fork_spec.rb
+++ b/spec/lib/gitlab/import_export/fork_spec.rb
@@ -7,7 +7,7 @@ describe 'forked project import' do
let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:project, name: 'test-repo-restorer-no-repo', path: 'test-repo-restorer-no-repo') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:forked_from_project) { create(:project, :repository) }
let(:forked_project) { fork_project(project_with_repo, nil, repository: true) }
let(:repo_saver) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json
index b6c1f0c81cb..62ef93f847a 100644
--- a/spec/lib/gitlab/import_export/project.json
+++ b/spec/lib/gitlab/import_export/project.json
@@ -14,8 +14,7 @@
"template": false,
"description": "",
"type": "ProjectLabel",
- "priorities": [
- ]
+ "priorities": []
},
{
"id": 3,
@@ -160,9 +159,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 352,
@@ -184,9 +181,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 353,
@@ -208,9 +203,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 354,
@@ -232,9 +225,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 355,
@@ -256,9 +247,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 356,
@@ -280,9 +269,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 357,
@@ -304,9 +291,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 358,
@@ -328,9 +313,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -395,9 +378,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 360,
@@ -419,9 +400,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 361,
@@ -443,9 +422,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 362,
@@ -467,9 +444,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 363,
@@ -491,9 +466,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 364,
@@ -515,9 +488,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 365,
@@ -539,9 +510,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 366,
@@ -563,9 +532,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -628,9 +595,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 368,
@@ -652,9 +617,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 369,
@@ -676,9 +639,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 370,
@@ -700,9 +661,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 371,
@@ -724,9 +683,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 372,
@@ -748,9 +705,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 373,
@@ -772,9 +727,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 374,
@@ -796,9 +749,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -840,9 +791,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 376,
@@ -864,9 +813,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 377,
@@ -888,9 +835,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 378,
@@ -912,9 +857,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 379,
@@ -936,9 +879,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 380,
@@ -960,9 +901,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 381,
@@ -984,9 +923,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 382,
@@ -1008,9 +945,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -1052,9 +987,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 384,
@@ -1076,9 +1009,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 385,
@@ -1100,9 +1031,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 386,
@@ -1124,9 +1053,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 387,
@@ -1148,9 +1075,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 388,
@@ -1172,9 +1097,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 389,
@@ -1196,9 +1119,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 390,
@@ -1220,9 +1141,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -1264,9 +1183,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 392,
@@ -1288,9 +1205,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 393,
@@ -1312,9 +1227,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 394,
@@ -1336,9 +1249,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 395,
@@ -1360,9 +1271,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 396,
@@ -1384,9 +1293,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 397,
@@ -1408,9 +1315,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 398,
@@ -1432,9 +1337,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -1476,9 +1379,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 400,
@@ -1500,9 +1401,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 401,
@@ -1524,9 +1423,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 402,
@@ -1548,9 +1445,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 403,
@@ -1572,9 +1467,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 404,
@@ -1596,9 +1489,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 405,
@@ -1620,9 +1511,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 406,
@@ -1644,9 +1533,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -1688,9 +1575,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 408,
@@ -1712,9 +1597,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 409,
@@ -1736,9 +1619,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 410,
@@ -1760,9 +1641,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 411,
@@ -1784,9 +1663,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 412,
@@ -1808,9 +1685,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 413,
@@ -1832,9 +1707,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 414,
@@ -1856,9 +1729,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -1900,9 +1771,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 416,
@@ -1924,9 +1793,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 417,
@@ -1948,9 +1815,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 418,
@@ -1972,9 +1837,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 419,
@@ -1996,9 +1859,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 420,
@@ -2020,9 +1881,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 421,
@@ -2044,9 +1903,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 422,
@@ -2068,9 +1925,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
},
@@ -2112,9 +1967,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 424,
@@ -2136,9 +1989,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 425,
@@ -2160,9 +2011,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 426,
@@ -2184,9 +2033,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 427,
@@ -2208,9 +2055,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 428,
@@ -2232,9 +2077,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 429,
@@ -2256,9 +2099,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 430,
@@ -2280,9 +2121,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
]
}
@@ -2378,12 +2217,8 @@
]
}
],
- "snippets": [
-
- ],
- "releases": [
-
- ],
+ "snippets": [],
+ "releases": [],
"project_members": [
{
"id": 36,
@@ -2515,9 +2350,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 672,
@@ -2539,9 +2372,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 673,
@@ -2563,9 +2394,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 674,
@@ -2587,9 +2416,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 675,
@@ -2611,9 +2438,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 676,
@@ -2635,9 +2460,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 677,
@@ -2659,9 +2482,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 678,
@@ -2683,9 +2504,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -2696,7 +2515,7 @@
"merge_request_diff_id": 27,
"relative_order": 0,
"sha": "bb5206fee213d983da88c47f9cf4cc6caf9c66dc",
- "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Feature conflcit added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-08-06T08:35:52.000+02:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2708,7 +2527,7 @@
"merge_request_diff_id": 27,
"relative_order": 1,
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T10:01:38.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2720,7 +2539,7 @@
"merge_request_diff_id": 27,
"relative_order": 2,
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:57:31.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2732,7 +2551,7 @@
"merge_request_diff_id": 27,
"relative_order": 3,
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:54:21.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2744,7 +2563,7 @@
"merge_request_diff_id": 27,
"relative_order": 4,
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:49:50.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2756,7 +2575,7 @@
"merge_request_diff_id": 27,
"relative_order": 5,
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:48:32.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -2834,7 +2653,7 @@
{
"merge_request_diff_id": 27,
"relative_order": 5,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n",
+ "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
"new_path": "files/ruby/popen.rb",
"old_path": "files/ruby/popen.rb",
"a_mode": "100644",
@@ -2958,9 +2777,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 680,
@@ -2982,9 +2799,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 681,
@@ -3006,9 +2821,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 682,
@@ -3030,9 +2843,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 683,
@@ -3054,9 +2865,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 684,
@@ -3078,9 +2887,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 685,
@@ -3102,9 +2909,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 686,
@@ -3126,9 +2931,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -3139,7 +2942,7 @@
"merge_request_diff_id": 26,
"sha": "0b4bc9a49b562e85de7cc9e834518ea6828729b9",
"relative_order": 0,
- "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Feature added\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:26:01.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3237,9 +3040,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 778,
@@ -3261,9 +3062,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 779,
@@ -3285,9 +3084,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 780,
@@ -3309,9 +3106,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 781,
@@ -3333,9 +3128,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 782,
@@ -3357,9 +3150,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 783,
@@ -3381,9 +3172,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 784,
@@ -3405,9 +3194,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -3516,9 +3303,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 786,
@@ -3540,9 +3325,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 787,
@@ -3564,9 +3347,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 788,
@@ -3588,9 +3369,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 789,
@@ -3612,9 +3391,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 790,
@@ -3636,9 +3413,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 791,
@@ -3660,9 +3435,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 792,
@@ -3684,9 +3457,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -3877,7 +3648,7 @@
"merge_request_diff_id": 14,
"relative_order": 15,
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T10:01:38.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3889,7 +3660,7 @@
"merge_request_diff_id": 14,
"relative_order": 16,
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:57:31.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3901,7 +3672,7 @@
"merge_request_diff_id": 14,
"relative_order": 17,
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:54:21.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3913,7 +3684,7 @@
"merge_request_diff_id": 14,
"relative_order": 18,
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:49:50.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -3925,7 +3696,7 @@
"merge_request_diff_id": 14,
"relative_order": 19,
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:48:32.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -4016,7 +3787,7 @@
{
"merge_request_diff_id": 14,
"relative_order": 6,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+ "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
"new_path": "files/images/wm.svg",
"old_path": "files/images/wm.svg",
"a_mode": "0",
@@ -4042,7 +3813,7 @@
{
"merge_request_diff_id": 14,
"relative_order": 8,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n",
+ "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
"new_path": "files/ruby/popen.rb",
"old_path": "files/ruby/popen.rb",
"a_mode": "100644",
@@ -4207,7 +3978,7 @@
},
"events": [
{
- "merge_request_diff_id": 14,
+ "merge_request_diff_id": 14,
"id": 529,
"target_type": "Note",
"target_id": 793,
@@ -4239,9 +4010,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 795,
@@ -4263,9 +4032,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 796,
@@ -4287,9 +4054,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 797,
@@ -4311,9 +4076,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 798,
@@ -4335,9 +4098,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 799,
@@ -4359,9 +4120,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 800,
@@ -4383,9 +4142,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -4603,7 +4360,7 @@
{
"merge_request_diff_id": 13,
"relative_order": 2,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+ "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
"new_path": "files/images/wm.svg",
"old_path": "files/images/wm.svg",
"a_mode": "0",
@@ -4740,9 +4497,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 802,
@@ -4764,9 +4519,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 803,
@@ -4788,9 +4541,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 804,
@@ -4812,9 +4563,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 805,
@@ -4836,9 +4585,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 806,
@@ -4860,9 +4607,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 807,
@@ -4884,9 +4629,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 808,
@@ -4908,9 +4651,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -5104,7 +4845,7 @@
{
"merge_request_diff_id": 12,
"relative_order": 2,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+ "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
"new_path": "files/images/wm.svg",
"old_path": "files/images/wm.svg",
"a_mode": "0",
@@ -5228,9 +4969,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 810,
@@ -5252,9 +4991,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 811,
@@ -5276,9 +5013,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 812,
@@ -5300,9 +5035,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 813,
@@ -5324,9 +5057,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 814,
@@ -5348,9 +5079,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 815,
@@ -5372,9 +5101,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 816,
@@ -5396,18 +5123,14 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
"id": 11,
"state": "empty",
- "merge_request_diff_commits": [
- ],
- "merge_request_diff_files": [
- ],
+ "merge_request_diff_commits": [],
+ "merge_request_diff_files": [],
"merge_request_id": 11,
"created_at": "2016-06-14T15:02:23.772Z",
"updated_at": "2016-06-14T15:02:23.833Z",
@@ -5482,9 +5205,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 818,
@@ -5506,9 +5227,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 819,
@@ -5530,9 +5249,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 820,
@@ -5554,9 +5271,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 821,
@@ -5578,9 +5293,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 822,
@@ -5602,9 +5315,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 823,
@@ -5626,9 +5337,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 824,
@@ -5650,9 +5359,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -5843,7 +5550,7 @@
"merge_request_diff_id": 10,
"relative_order": 16,
"sha": "5937ac0a7beb003549fc5fd26fc247adbce4a52e",
- "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Add submodule from gitlab.com\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T10:01:38.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5855,7 +5562,7 @@
"merge_request_diff_id": 10,
"relative_order": 17,
"sha": "570e7b2abdd848b95f2f578043fc23bd6f6fd24d",
- "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Change some files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:57:31.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5867,7 +5574,7 @@
"merge_request_diff_id": 10,
"relative_order": 18,
"sha": "6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9",
- "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "More submodules\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:54:21.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5879,7 +5586,7 @@
"merge_request_diff_id": 10,
"relative_order": 19,
"sha": "d14d6c0abdd253381df51a723d58691b2ee1ab08",
- "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Remove ds_store files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:49:50.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5891,7 +5598,7 @@
"merge_request_diff_id": 10,
"relative_order": 20,
"sha": "c1acaa58bbcbc3eafe538cb8274ba387047b69f8",
- "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n",
+ "message": "Ignore DS files\n\nSigned-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>\n",
"authored_date": "2014-02-27T09:48:32.000+01:00",
"author_name": "Dmitriy Zaporozhets",
"author_email": "dmitriy.zaporozhets@gmail.com",
@@ -5982,7 +5689,7 @@
{
"merge_request_diff_id": 10,
"relative_order": 6,
- "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+\u003c?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?\u003e\n+\u003csvg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\"\u003e\n+ \u003c!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch --\u003e\n+ \u003ctitle\u003ewm\u003c/title\u003e\n+ \u003cdesc\u003eCreated with Sketch.\u003c/desc\u003e\n+ \u003cdefs\u003e\n+ \u003cpath id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"\u003e\u003c/path\u003e\n+ \u003c/defs\u003e\n+ \u003cg id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\"\u003e\n+ \u003cpath d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\"\u003e\n+ \u003cg id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\"\u003e\n+ \u003cg id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\n+ \u003cpath d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g16\"\u003e\n+ \u003cg id=\"g18-Clipped\"\u003e\n+ \u003cmask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\"\u003e\n+ \u003cuse xlink:href=\"#path-1\"\u003e\u003c/use\u003e\n+ \u003c/mask\u003e\n+ \u003cg id=\"path22\"\u003e\u003c/g\u003e\n+ \u003cg id=\"g18\" mask=\"url(#mask-2)\"\u003e\n+ \u003cg transform=\"translate(382.736659, 312.879425)\"\u003e\n+ \u003cg id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\"\u003e\n+ \u003cpath d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\"\u003e\n+ \u003cpath d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\"\u003e\n+ \u003cpath d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\"\u003e\n+ \u003cpath d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cpath d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cpath d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003cg id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\"\u003e\n+ \u003cpath d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\"\u003e\n+ \u003cpath d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path54\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\"\u003e\n+ \u003cpath d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cg id=\"path62\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\"\u003e\n+ \u003cpath d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\"\u003e\n+ \u003cg id=\"path70\"\u003e\u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\"\u003e\n+ \u003cpath d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\"\u003e\n+ \u003cpath d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\"\u003e\n+ \u003cpath d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003cg id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\"\u003e\n+ \u003cpath d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"\u003e\u003c/path\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+ \u003c/g\u003e\n+\u003c/svg\u003e\n\\ No newline at end of file\n",
+ "utf8_diff": "--- /dev/null\n+++ b/files/images/wm.svg\n@@ -0,0 +1,78 @@\n+<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n+<svg width=\"1300px\" height=\"680px\" viewBox=\"0 0 1300 680\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" xmlns:sketch=\"http://www.bohemiancoding.com/sketch/ns\">\n+ <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->\n+ <title>wm</title>\n+ <desc>Created with Sketch.</desc>\n+ <defs>\n+ <path id=\"path-1\" d=\"M-69.8,1023.54607 L1675.19996,1023.54607 L1675.19996,0 L-69.8,0 L-69.8,1023.54607 L-69.8,1023.54607 Z\"></path>\n+ </defs>\n+ <g id=\"Page-1\" stroke=\"none\" stroke-width=\"1\" fill=\"none\" fill-rule=\"evenodd\" sketch:type=\"MSPage\">\n+ <path d=\"M1300,680 L0,680 L0,0 L1300,0 L1300,680 L1300,680 Z\" id=\"bg\" fill=\"#30353E\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"gitlab_logo\" sketch:type=\"MSLayerGroup\" transform=\"translate(-262.000000, -172.000000)\">\n+ <g id=\"g10\" transform=\"translate(872.500000, 512.354581) scale(1, -1) translate(-872.500000, -512.354581) translate(0.000000, 0.290751)\">\n+ <g id=\"g12\" transform=\"translate(1218.022652, 440.744871)\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\">\n+ <path d=\"M-50.0233338,141.900706 L-69.07059,141.900706 L-69.0100967,0.155858152 L8.04444805,0.155858152 L8.04444805,17.6840847 L-49.9628405,17.6840847 L-50.0233338,141.900706 L-50.0233338,141.900706 Z\" id=\"path14\"></path>\n+ </g>\n+ <g id=\"g16\">\n+ <g id=\"g18-Clipped\">\n+ <mask id=\"mask-2\" sketch:name=\"path22\" fill=\"white\">\n+ <use xlink:href=\"#path-1\"></use>\n+ </mask>\n+ <g id=\"path22\"></g>\n+ <g id=\"g18\" mask=\"url(#mask-2)\">\n+ <g transform=\"translate(382.736659, 312.879425)\">\n+ <g id=\"g24\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(852.718192, 124.992771)\">\n+ <path d=\"M63.9833317,27.9148929 C59.2218085,22.9379001 51.2134221,17.9597442 40.3909323,17.9597442 C25.8888194,17.9597442 20.0453962,25.1013043 20.0453962,34.4074318 C20.0453962,48.4730484 29.7848226,55.1819277 50.5642821,55.1819277 C54.4602853,55.1819277 60.7364685,54.7492469 63.9833317,54.1002256 L63.9833317,27.9148929 L63.9833317,27.9148929 Z M44.2869356,113.827628 C28.9053426,113.827628 14.7975996,108.376082 3.78897657,99.301416 L10.5211864,87.6422957 C18.3131929,92.1866076 27.8374026,96.7320827 41.4728323,96.7320827 C57.0568452,96.7320827 63.9833317,88.7239978 63.9833317,75.3074024 L63.9833317,68.3821827 C60.9528485,69.0312039 54.6766653,69.4650479 50.7806621,69.4650479 C17.4476729,69.4650479 0.565379986,57.7791759 0.565379986,33.3245665 C0.565379986,11.4683685 13.9844297,0.43151772 34.3299658,0.43151772 C48.0351955,0.43151772 61.1692285,6.70771614 65.7143717,16.8780421 L69.1776149,3.02876588 L82.5978279,3.02876588 L82.5978279,75.5237428 C82.5978279,98.462806 72.6408582,113.827628 44.2869356,113.827628 L44.2869356,113.827628 Z\" id=\"path26\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g28\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(959.546624, 124.857151)\">\n+ <path d=\"M37.2266657,17.4468081 C30.0837992,17.4468081 23.8064527,18.3121698 19.0449295,20.4767371 L19.0449295,79.2306079 L19.0449295,86.0464943 C25.538656,91.457331 33.5470425,95.3526217 43.7203922,95.3526217 C62.1173451,95.3526217 69.2602116,82.3687072 69.2602116,61.3767077 C69.2602116,31.5135879 57.7885819,17.4468081 37.2266657,17.4468081 M45.2315622,113.963713 C28.208506,113.963713 19.0449295,102.384849 19.0449295,102.384849 L19.0449295,120.67143 L18.9844362,144.908535 L10.3967097,144.908535 L0.371103324,144.908535 L0.431596656,6.62629771 C9.73826309,2.73100702 22.5081728,0.567602823 36.3611458,0.567602823 C71.8579349,0.567602823 88.9566078,23.2891625 88.9566078,62.4584098 C88.9566078,93.4043948 73.1527248,113.963713 45.2315622,113.963713\" id=\"path30\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g32\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(509.576747, 125.294950)\">\n+ <path d=\"M68.636665,129.10638 C85.5189579,129.10638 96.3414476,123.480366 103.484314,117.853189 L111.669527,132.029302 C100.513161,141.811145 85.5073245,147.06845 69.5021849,147.06845 C29.0274926,147.06845 0.673569983,122.3975 0.673569983,72.6252464 C0.673569983,20.4709215 31.2622559,0.12910638 66.2553217,0.12910638 C83.7879179,0.12910638 98.7227909,4.24073748 108.462217,8.35236859 L108.063194,64.0763105 L108.063194,70.6502677 L108.063194,81.6057001 L56.1168719,81.6057001 L56.1168719,64.0763105 L89.2323178,64.0763105 L89.6313411,21.7701271 C85.3025779,19.6055598 77.7269514,17.8748364 67.554765,17.8748364 C39.4172223,17.8748364 20.5863462,35.5717154 20.5863462,72.8415868 C20.5863462,110.711628 40.0663623,129.10638 68.636665,129.10638\" id=\"path34\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g36\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(692.388992, 124.376085)\">\n+ <path d=\"M19.7766662,145.390067 L1.16216997,145.390067 L1.2226633,121.585642 L1.2226633,111.846834 L1.2226633,106.170806 L1.2226633,96.2656714 L1.2226633,39.5681976 L1.2226633,39.3518572 C1.2226633,16.4127939 11.1796331,1.04797161 39.5335557,1.04797161 C43.4504989,1.04797161 47.2836822,1.40388649 51.0051854,2.07965952 L51.0051854,18.7925385 C48.3109055,18.3796307 45.4351455,18.1446804 42.3476589,18.1446804 C26.763646,18.1446804 19.8371595,26.1516022 19.8371595,39.5681976 L19.8371595,96.2656714 L51.0051854,96.2656714 L51.0051854,111.846834 L19.8371595,111.846834 L19.7766662,145.390067 L19.7766662,145.390067 Z\" id=\"path38\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <path d=\"M646.318899,128.021188 L664.933395,128.021188 L664.933395,236.223966 L646.318899,236.223966 L646.318899,128.021188 L646.318899,128.021188 Z\" id=\"path40\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <path d=\"M646.318899,251.154944 L664.933395,251.154944 L664.933395,269.766036 L646.318899,269.766036 L646.318899,251.154944 L646.318899,251.154944 Z\" id=\"path42\" fill=\"#8C929D\" sketch:type=\"MSShapeGroup\"></path>\n+ <g id=\"g44\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.464170, 0.676006)\">\n+ <path d=\"M429.269989,169.815599 L405.225053,243.802859 L357.571431,390.440955 C355.120288,397.984955 344.444378,397.984955 341.992071,390.440955 L294.337286,243.802859 L136.094873,243.802859 L88.4389245,390.440955 C85.9877812,397.984955 75.3118715,397.984955 72.8595648,390.440955 L25.2059427,243.802859 L1.16216997,169.815599 C-1.03187664,163.067173 1.37156997,155.674379 7.11261982,151.503429 L215.215498,0.336141836 L423.319539,151.503429 C429.060589,155.674379 431.462873,163.067173 429.269989,169.815599\" id=\"path46\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g48\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(135.410135, 1.012147)\">\n+ <path d=\"M80.269998,0 L80.269998,0 L159.391786,243.466717 L1.14820997,243.466717 L80.269998,0 L80.269998,0 Z\" id=\"path50\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g52\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path54\"></g>\n+ </g>\n+ <g id=\"g56\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(24.893471, 1.012613)\">\n+ <path d=\"M190.786662,0 L111.664874,243.465554 L0.777106647,243.465554 L190.786662,0 L190.786662,0 Z\" id=\"path58\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g60\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <g id=\"path62\"></g>\n+ </g>\n+ <g id=\"g64\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(0.077245, 0.223203)\">\n+ <path d=\"M25.5933327,244.255313 L25.5933327,244.255313 L1.54839663,170.268052 C-0.644486651,163.519627 1.75779662,156.126833 7.50000981,151.957046 L215.602888,0.789758846 L25.5933327,244.255313 L25.5933327,244.255313 Z\" id=\"path66\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g68\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012147)\">\n+ <g id=\"path70\"></g>\n+ </g>\n+ <g id=\"g72\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(25.670578, 244.478283)\">\n+ <path d=\"M0,0 L110.887767,0 L63.2329818,146.638096 C60.7806751,154.183259 50.1047654,154.183259 47.6536221,146.638096 L0,0 L0,0 Z\" id=\"path74\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g76\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(215.680133, 1.012613)\">\n+ <path d=\"M0,0 L79.121788,243.465554 L190.009555,243.465554 L0,0 L0,0 Z\" id=\"path78\" fill=\"#FC6D26\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g80\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(214.902910, 0.223203)\">\n+ <path d=\"M190.786662,244.255313 L190.786662,244.255313 L214.831598,170.268052 C217.024481,163.519627 214.622198,156.126833 208.879985,151.957046 L0.777106647,0.789758846 L190.786662,244.255313 L190.786662,244.255313 Z\" id=\"path82\" fill=\"#FCA326\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ <g id=\"g84\" stroke-width=\"1\" fill=\"none\" sketch:type=\"MSLayerGroup\" transform=\"translate(294.009575, 244.478283)\">\n+ <path d=\"M111.679997,0 L0.79222998,0 L48.4470155,146.638096 C50.8993221,154.183259 61.5752318,154.183259 64.0263751,146.638096 L111.679997,0 L111.679997,0 Z\" id=\"path86\" fill=\"#E24329\" sketch:type=\"MSShapeGroup\"></path>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+ </g>\n+</svg>\n\\ No newline at end of file\n",
"new_path": "files/images/wm.svg",
"old_path": "files/images/wm.svg",
"a_mode": "0",
@@ -6008,7 +5715,7 @@
{
"merge_request_diff_id": 10,
"relative_order": 8,
- "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" =\u003e path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" =\u003e path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output \u003c\u003c stdout.read\n @cmd_output \u003c\u003c stderr.read\n",
+ "utf8_diff": "--- a/files/ruby/popen.rb\n+++ b/files/ruby/popen.rb\n@@ -6,12 +6,18 @@ module Popen\n \n def popen(cmd, path=nil)\n unless cmd.is_a?(Array)\n- raise \"System commands must be given as an array of strings\"\n+ raise RuntimeError, \"System commands must be given as an array of strings\"\n end\n \n path ||= Dir.pwd\n- vars = { \"PWD\" => path }\n- options = { chdir: path }\n+\n+ vars = {\n+ \"PWD\" => path\n+ }\n+\n+ options = {\n+ chdir: path\n+ }\n \n unless File.directory?(path)\n FileUtils.mkdir_p(path)\n@@ -19,6 +25,7 @@ module Popen\n \n @cmd_output = \"\"\n @cmd_status = 0\n+\n Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|\n @cmd_output << stdout.read\n @cmd_output << stderr.read\n",
"new_path": "files/ruby/popen.rb",
"old_path": "files/ruby/popen.rb",
"a_mode": "100644",
@@ -6171,9 +5878,7 @@
"author": {
"name": "User 4"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 826,
@@ -6195,9 +5900,7 @@
"author": {
"name": "User 3"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 827,
@@ -6219,9 +5922,7 @@
"author": {
"name": "User 0"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 828,
@@ -6243,9 +5944,7 @@
"author": {
"name": "Ottis Schuster II"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 829,
@@ -6267,9 +5966,7 @@
"author": {
"name": "Rhett Emmerich IV"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 830,
@@ -6291,9 +5988,7 @@
"author": {
"name": "Burdette Bernier"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 831,
@@ -6315,9 +6010,7 @@
"author": {
"name": "Ari Wintheiser"
},
- "events": [
-
- ]
+ "events": []
},
{
"id": 832,
@@ -6339,9 +6032,7 @@
"author": {
"name": "Administrator"
},
- "events": [
-
- ]
+ "events": []
}
],
"merge_request_diff": {
@@ -6953,9 +6644,7 @@
"updated_at": "2017-01-16T15:25:28.637Z"
}
],
- "deploy_keys": [
-
- ],
+ "deploy_keys": [],
"services": [
{
"id": 100,
@@ -6964,9 +6653,7 @@
"created_at": "2016-06-14T15:01:51.315Z",
"updated_at": "2016-06-14T15:01:51.315Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7008,9 +6695,7 @@
"created_at": "2016-06-14T15:01:51.289Z",
"updated_at": "2016-06-14T15:01:51.289Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7030,9 +6715,7 @@
"created_at": "2016-06-14T15:01:51.277Z",
"updated_at": "2016-06-14T15:01:51.277Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7052,9 +6735,7 @@
"created_at": "2016-06-14T15:01:51.267Z",
"updated_at": "2016-06-14T15:01:51.267Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7097,9 +6778,7 @@
"created_at": "2016-06-14T15:01:51.232Z",
"updated_at": "2016-06-14T15:01:51.232Z",
"active": true,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7141,9 +6820,7 @@
"created_at": "2016-06-14T15:01:51.202Z",
"updated_at": "2016-06-14T15:01:51.202Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7163,9 +6840,7 @@
"created_at": "2016-06-14T15:01:51.182Z",
"updated_at": "2016-06-14T15:01:51.182Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7185,9 +6860,7 @@
"created_at": "2016-06-14T15:01:51.166Z",
"updated_at": "2016-06-14T15:01:51.166Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7207,9 +6880,7 @@
"created_at": "2016-06-14T15:01:51.153Z",
"updated_at": "2016-06-14T15:01:51.153Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7229,9 +6900,7 @@
"created_at": "2016-06-14T15:01:51.139Z",
"updated_at": "2016-06-14T15:01:51.139Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7251,9 +6920,7 @@
"created_at": "2016-06-14T15:01:51.125Z",
"updated_at": "2016-06-14T15:01:51.125Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7273,9 +6940,7 @@
"created_at": "2016-06-14T15:01:51.113Z",
"updated_at": "2016-06-14T15:01:51.113Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7295,9 +6960,7 @@
"created_at": "2016-06-14T15:01:51.080Z",
"updated_at": "2016-06-14T15:01:51.080Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7317,9 +6980,7 @@
"created_at": "2016-06-14T15:01:51.067Z",
"updated_at": "2016-06-14T15:01:51.067Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7339,9 +7000,7 @@
"created_at": "2016-06-14T15:01:51.047Z",
"updated_at": "2016-06-14T15:01:51.047Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7361,9 +7020,7 @@
"created_at": "2016-06-14T15:01:51.031Z",
"updated_at": "2016-06-14T15:01:51.031Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7383,9 +7040,7 @@
"created_at": "2016-06-14T15:01:51.031Z",
"updated_at": "2016-06-14T15:01:51.031Z",
"active": false,
- "properties": {
-
- },
+ "properties": {},
"template": false,
"push_events": true,
"issues_events": true,
@@ -7399,9 +7054,7 @@
"type": "JenkinsDeprecatedService"
}
],
- "hooks": [
-
- ],
+ "hooks": [],
"protected_branches": [
{
"id": 1,
@@ -7475,5 +7128,25 @@
"key": "bar",
"value": "bar"
}
+ ],
+ "project_badges": [
+ {
+ "id": 1,
+ "created_at": "2017-10-19T15:36:23.466Z",
+ "updated_at": "2017-10-19T15:36:23.466Z",
+ "project_id": 5,
+ "type": "ProjectBadge",
+ "link_url": "http://www.example.com",
+ "image_url": "http://www.example.com"
+ },
+ {
+ "id": 2,
+ "created_at": "2017-10-19T15:36:23.466Z",
+ "updated_at": "2017-10-19T15:36:23.466Z",
+ "project_id": 5,
+ "type": "ProjectBadge",
+ "link_url": "http://www.example.com",
+ "image_url": "http://www.example.com"
+ }
]
}
diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
index d076007e4bc..f4e466d1296 100644
--- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb
@@ -7,9 +7,9 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
@user = create(:user)
RSpec::Mocks.with_temporary_scope do
- @shared = Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path')
- allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
@project = create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project')
+ @shared = @project.import_export_shared
+ allow(@shared).to receive(:export_path).and_return('spec/lib/gitlab/import_export/')
allow_any_instance_of(Repository).to receive(:fetch_ref).and_return(true)
allow_any_instance_of(Gitlab::Git::Repository).to receive(:branch_exists?).and_return(false)
@@ -129,6 +129,10 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
expect(@project.custom_attributes.count).to eq(2)
end
+ it 'has badges' do
+ expect(@project.project_badges.count).to eq(2)
+ end
+
it 'restores the correct service' do
expect(CustomIssueTrackerService.first).not_to be_nil
end
@@ -259,7 +263,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do
context 'Light JSON' do
let(:user) { create(:user) }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: "", project_path: 'path') }
+ let(:shared) { project.import_export_shared }
let!(:project) { create(:project, :builds_disabled, :issues_disabled, name: 'project', path: 'project') }
let(:project_tree_restorer) { described_class.new(user: user, shared: shared, project: project) }
let(:restored_project_json) { project_tree_restorer.restore }
diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
index 5804c45871e..3049491f0ae 100644
--- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::ProjectTreeSaver do
describe 'saves the project tree into a json object' do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:project_tree_saver) { described_class.new(project: project, current_user: user, shared: shared) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
let(:user) { create(:user) }
@@ -180,6 +180,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
expect(saved_project_json['custom_attributes'].count).to eq(2)
end
+ it 'has badges' do
+ expect(saved_project_json['project_badges'].count).to eq(2)
+ end
+
it 'does not complain about non UTF-8 characters in MR diff files' do
ActiveRecord::Base.connection.execute("UPDATE merge_request_diff_files SET diff = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'")
@@ -288,6 +292,9 @@ describe Gitlab::ImportExport::ProjectTreeSaver do
create(:project_custom_attribute, project: project)
create(:project_custom_attribute, project: project)
+ create(:project_badge, project: project)
+ create(:project_badge, project: project)
+
project
end
diff --git a/spec/lib/gitlab/import_export/reader_spec.rb b/spec/lib/gitlab/import_export/reader_spec.rb
index e9f5273725d..1ef024d3078 100644
--- a/spec/lib/gitlab/import_export/reader_spec.rb
+++ b/spec/lib/gitlab/import_export/reader_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe Gitlab::ImportExport::Reader do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+ let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
let(:test_config) { 'spec/support/import_export/import_export.yml' }
let(:project_tree_hash) do
{
diff --git a/spec/lib/gitlab/import_export/repo_restorer_spec.rb b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
index c49af602a01..dc806d036ff 100644
--- a/spec/lib/gitlab/import_export/repo_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_restorer_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::ImportExport::RepoRestorer do
let!(:project_with_repo) { create(:project, :repository, name: 'test-repo-restorer', path: 'test-repo-restorer') }
let!(:project) { create(:project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:bundler) { Gitlab::ImportExport::RepoSaver.new(project: project_with_repo, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
let(:restorer) do
diff --git a/spec/lib/gitlab/import_export/repo_saver_spec.rb b/spec/lib/gitlab/import_export/repo_saver_spec.rb
index 44f972fe530..187ec8fcfa2 100644
--- a/spec/lib/gitlab/import_export/repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/repo_saver_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::RepoSaver do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, name: 'searchable_project') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:bundler) { described_class.new(project: project, shared: shared) }
before do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index feaab6673cd..ddcbb7a0033 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -536,3 +536,12 @@ LfsFileLock:
- user_id
- project_id
- created_at
+Badge:
+- id
+- link_url
+- image_url
+- project_id
+- group_id
+- created_at
+- updated_at
+- type
diff --git a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
index 8a3a244be21..acef97459b8 100644
--- a/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_restorer_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe Gitlab::ImportExport::UploadsRestorer do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/lib/gitlab/import_export/uploads_saver_spec.rb b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
index 177036c109b..1304d8fabfc 100644
--- a/spec/lib/gitlab/import_export/uploads_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/uploads_saver_spec.rb
@@ -4,7 +4,7 @@ describe Gitlab::ImportExport::UploadsSaver do
describe 'bundle a project Git repo' do
let(:export_path) { "#{Dir.tmpdir}/uploads_saver_spec" }
let(:file) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
before do
allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb
index e7d50f75682..49d857d9483 100644
--- a/spec/lib/gitlab/import_export/version_checker_spec.rb
+++ b/spec/lib/gitlab/import_export/version_checker_spec.rb
@@ -2,12 +2,13 @@ require 'spec_helper'
include ImportExport::CommonUtil
describe Gitlab::ImportExport::VersionChecker do
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') }
+ let(:shared) { Gitlab::ImportExport::Shared.new(nil) }
describe 'bundle a project Git repo' do
let(:version) { Gitlab::ImportExport.version }
before do
+ allow_any_instance_of(Gitlab::ImportExport::Shared).to receive(:relative_archive_path).and_return('')
allow(File).to receive(:open).and_return(version)
end
diff --git a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
index 1d1e7e7f89a..d2bd8ccdf3f 100644
--- a/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_repo_saver_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::ImportExport::WikiRepoSaver do
let(:user) { create(:user) }
let!(:project) { create(:project, :public, name: 'searchable_project') }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:wiki_bundler) { described_class.new(project: project, shared: shared) }
let!(:project_wiki) { ProjectWiki.new(project, user) }
diff --git a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
index 81b654e9c5f..5c01ee0ebb8 100644
--- a/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
+++ b/spec/lib/gitlab/import_export/wiki_restorer_spec.rb
@@ -6,7 +6,7 @@ describe Gitlab::ImportExport::WikiRestorer do
let!(:project_without_wiki) { create(:project) }
let!(:project) { create(:project) }
let(:export_path) { "#{Dir.tmpdir}/project_tree_saver_spec" }
- let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: project.full_path) }
+ let(:shared) { project.import_export_shared }
let(:bundler) { Gitlab::ImportExport::WikiRepoSaver.new(project: project_with_wiki, shared: shared) }
let(:bundle_path) { File.join(shared.export_path, Gitlab::ImportExport.project_bundle_filename) }
let(:restorer) do
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index 07ba11b93a3..39ec2f37a83 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -11,15 +11,17 @@ describe Gitlab::Middleware::ReadOnly do
RSpec::Matchers.define :disallow_request do
match do |middleware|
- flash = middleware.send(:rack_flash)
- flash['alert'] && flash['alert'].include?('You cannot do writing operations')
+ alert = middleware.env['rack.session'].to_hash
+ .dig('flash', 'flashes', 'alert')
+
+ alert&.include?('You cannot perform write operations')
end
end
RSpec::Matchers.define :disallow_request_in_json do
match do |response|
json_response = JSON.parse(response.body)
- response.body.include?('You cannot do writing operations') && json_response.key?('message')
+ response.body.include?('You cannot perform write operations') && json_response.key?('message')
end
end
@@ -34,10 +36,25 @@ describe Gitlab::Middleware::ReadOnly do
rack.to_app
end
- subject { described_class.new(fake_app) }
+ let(:observe_env) do
+ Module.new do
+ attr_reader :env
+
+ def call(env)
+ @env = env
+ super
+ end
+ end
+ end
let(:request) { Rack::MockRequest.new(rack_stack) }
+ subject do
+ described_class.new(fake_app).tap do |app|
+ app.extend(observe_env)
+ end
+ end
+
context 'normal requests to a read-only Gitlab instance' do
let(:fake_app) { lambda { |env| [200, { 'Content-Type' => 'text/plain' }, ['OK']] } }
diff --git a/spec/lib/gitlab/middleware/release_env_spec.rb b/spec/lib/gitlab/middleware/release_env_spec.rb
new file mode 100644
index 00000000000..5e3aa877409
--- /dev/null
+++ b/spec/lib/gitlab/middleware/release_env_spec.rb
@@ -0,0 +1,16 @@
+require 'spec_helper'
+
+describe Gitlab::Middleware::ReleaseEnv do
+ let(:inner_app) { double(:app, call: 'yay') }
+ let(:app) { described_class.new(inner_app) }
+ let(:env) { { 'action_controller.instance' => 'something' } }
+
+ describe '#call' do
+ it 'calls the app and clears the env' do
+ result = app.call(env)
+
+ expect(result).to eq('yay')
+ expect(env).to be_empty
+ end
+ end
+end
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index d8250e4b4c6..c46bb8edebf 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -217,7 +217,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
- expect(results.issues_count).to eq 1
+ expect(results.limited_issues_count).to eq 1
end
it 'does not list project confidential issues for project members with guest role' do
@@ -229,7 +229,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).not_to include security_issue_2
- expect(results.issues_count).to eq 1
+ expect(results.limited_issues_count).to eq 1
end
it 'lists project confidential issues for author' do
@@ -239,7 +239,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).not_to include security_issue_2
- expect(results.issues_count).to eq 2
+ expect(results.limited_issues_count).to eq 2
end
it 'lists project confidential issues for assignee' do
@@ -249,7 +249,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).not_to include security_issue_1
expect(issues).to include security_issue_2
- expect(results.issues_count).to eq 2
+ expect(results.limited_issues_count).to eq 2
end
it 'lists project confidential issues for project members' do
@@ -261,7 +261,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
- expect(results.issues_count).to eq 3
+ expect(results.limited_issues_count).to eq 3
end
it 'lists all project issues for admin' do
@@ -271,7 +271,7 @@ describe Gitlab::ProjectSearchResults do
expect(issues).to include issue
expect(issues).to include security_issue_1
expect(issues).to include security_issue_2
- expect(results.issues_count).to eq 3
+ expect(results.limited_issues_count).to eq 3
end
end
@@ -304,6 +304,35 @@ describe Gitlab::ProjectSearchResults do
end
end
+ describe '#limited_notes_count' do
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_issue, project: project) }
+ let(:results) { described_class.new(user, project, note.note) }
+
+ context 'when count_limit is lower than total amount' do
+ before do
+ allow(results).to receive(:count_limit).and_return(1)
+ end
+
+ it 'calls note finder once to get the limited amount of notes' do
+ expect(results).to receive(:notes_finder).once.and_call_original
+ expect(results.limited_notes_count).to eq(1)
+ end
+ end
+
+ context 'when count_limit is higher than total amount' do
+ it 'calls note finder multiple times to get the limited amount of notes' do
+ project = create(:project, :public)
+ note = create(:note_on_issue, project: project)
+
+ results = described_class.new(user, project, note.note)
+
+ expect(results).to receive(:notes_finder).exactly(4).times.and_call_original
+ expect(results.limited_notes_count).to eq(1)
+ end
+ end
+ end
+
# Examples for commit access level test
#
# params:
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 9dbab95f70e..87288baedb0 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -29,30 +29,6 @@ describe Gitlab::SearchResults do
end
end
- describe '#projects_count' do
- it 'returns the total amount of projects' do
- expect(results.projects_count).to eq(1)
- end
- end
-
- describe '#issues_count' do
- it 'returns the total amount of issues' do
- expect(results.issues_count).to eq(1)
- end
- end
-
- describe '#merge_requests_count' do
- it 'returns the total amount of merge requests' do
- expect(results.merge_requests_count).to eq(1)
- end
- end
-
- describe '#milestones_count' do
- it 'returns the total amount of milestones' do
- expect(results.milestones_count).to eq(1)
- end
- end
-
context "when count_limit is lower than total amount" do
before do
allow(results).to receive(:count_limit).and_return(1)
@@ -183,7 +159,7 @@ describe Gitlab::SearchResults do
expect(issues).not_to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 1
+ expect(results.limited_issues_count).to eq 1
end
it 'does not list confidential issues for project members with guest role' do
@@ -199,7 +175,7 @@ describe Gitlab::SearchResults do
expect(issues).not_to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 1
+ expect(results.limited_issues_count).to eq 1
end
it 'lists confidential issues for author' do
@@ -212,7 +188,7 @@ describe Gitlab::SearchResults do
expect(issues).to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 3
+ expect(results.limited_issues_count).to eq 3
end
it 'lists confidential issues for assignee' do
@@ -225,7 +201,7 @@ describe Gitlab::SearchResults do
expect(issues).not_to include security_issue_3
expect(issues).to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 3
+ expect(results.limited_issues_count).to eq 3
end
it 'lists confidential issues for project members' do
@@ -241,7 +217,7 @@ describe Gitlab::SearchResults do
expect(issues).to include security_issue_3
expect(issues).not_to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 4
+ expect(results.limited_issues_count).to eq 4
end
it 'lists all issues for admin' do
@@ -254,7 +230,7 @@ describe Gitlab::SearchResults do
expect(issues).to include security_issue_3
expect(issues).to include security_issue_4
expect(issues).not_to include security_issue_5
- expect(results.issues_count).to eq 5
+ expect(results.limited_issues_count).to eq 5
end
end
diff --git a/spec/lib/gitlab/string_placeholder_replacer_spec.rb b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
new file mode 100644
index 00000000000..7a03ea4154c
--- /dev/null
+++ b/spec/lib/gitlab/string_placeholder_replacer_spec.rb
@@ -0,0 +1,38 @@
+require 'spec_helper'
+
+describe Gitlab::StringPlaceholderReplacer do
+ describe '.render_url' do
+ it 'returns the nil if the string is blank' do
+ expect(described_class.replace_string_placeholders(nil, /whatever/)).to be_blank
+ end
+
+ it 'returns the string if the placeholder regex' do
+ expect(described_class.replace_string_placeholders('whatever')).to eq 'whatever'
+ end
+
+ it 'returns the string if no block given' do
+ expect(described_class.replace_string_placeholders('whatever', /whatever/)).to eq 'whatever'
+ end
+
+ context 'when all params are valid' do
+ let(:string) { '%{path}/%{id}/%{branch}' }
+ let(:regex) { /(path|id)/ }
+
+ it 'replaces each placeholders with the block result' do
+ result = described_class.replace_string_placeholders(string, regex) do |arg|
+ 'WHATEVER'
+ end
+
+ expect(result).to eq 'WHATEVER/WHATEVER/%{branch}'
+ end
+
+ it 'does not replace the placeholder if the block result is nil' do
+ result = described_class.replace_string_placeholders(string, regex) do |arg|
+ arg == 'path' ? nil : 'WHATEVER'
+ end
+
+ expect(result).to eq '%{path}/WHATEVER/%{branch}'
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/string_regex_marker_spec.rb b/spec/lib/gitlab/string_regex_marker_spec.rb
index d715f9bd641..37b1298b962 100644
--- a/spec/lib/gitlab/string_regex_marker_spec.rb
+++ b/spec/lib/gitlab/string_regex_marker_spec.rb
@@ -2,17 +2,36 @@ require 'spec_helper'
describe Gitlab::StringRegexMarker do
describe '#mark' do
- let(:raw) { %{"name": "AFNetworking"} }
- let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
- subject do
- described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
- %{<a href="#">#{text}</a>}
+ context 'with a single occurrence' do
+ let(:raw) { %{"name": "AFNetworking"} }
+ let(:rich) { %{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"AFNetworking"</span>}.html_safe }
+
+ subject do
+ described_class.new(raw, rich).mark(/"[^"]+":\s*"(?<name>[^"]+)"/, group: :name) do |text, left:, right:|
+ %{<a href="#">#{text}</a>}
+ end
+ end
+
+ it 'marks the match' do
+ expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
+ expect(subject).to be_html_safe
end
end
- it 'marks the inline diffs' do
- expect(subject).to eq(%{<span class="key">"name"</span><span class="punctuation">: </span><span class="value">"<a href="#">AFNetworking</a>"</span>})
- expect(subject).to be_html_safe
+ context 'with multiple occurrences' do
+ let(:raw) { %{a <b> <c> d} }
+ let(:rich) { %{a &lt;b&gt; &lt;c&gt; d}.html_safe }
+
+ subject do
+ described_class.new(raw, rich).mark(/<[a-z]>/) do |text, left:, right:|
+ %{<strong>#{text}</strong>}
+ end
+ end
+
+ it 'marks the matches' do
+ expect(subject).to eq(%{a <strong>&lt;b&gt;</strong> <strong>&lt;c&gt;</strong> d})
+ expect(subject).to be_html_safe
+ end
end
end
end
diff --git a/spec/lib/gitlab/verify/lfs_objects_spec.rb b/spec/lib/gitlab/verify/lfs_objects_spec.rb
new file mode 100644
index 00000000000..64f3a9660e0
--- /dev/null
+++ b/spec/lib/gitlab/verify/lfs_objects_spec.rb
@@ -0,0 +1,35 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::LfsObjects do
+ include GitlabVerifyHelpers
+
+ it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+ let!(:objects) { create_list(:lfs_object, 3, :with_file) }
+ end
+
+ describe '#run_batches' do
+ let(:failures) { collect_failures }
+ let(:failure) { failures[lfs_object] }
+
+ let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
+
+ it 'passes LFS objects with the correct file' do
+ expect(failures).to eq({})
+ end
+
+ it 'fails LFS objects with a missing file' do
+ FileUtils.rm_f(lfs_object.file.path)
+
+ expect(failures.keys).to contain_exactly(lfs_object)
+ expect(failure).to be_a(Errno::ENOENT)
+ expect(failure.to_s).to include(lfs_object.file.path)
+ end
+
+ it 'fails LFS objects with a mismatched oid' do
+ File.truncate(lfs_object.file.path, 0)
+
+ expect(failures.keys).to contain_exactly(lfs_object)
+ expect(failure.to_s).to include('Checksum mismatch')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/verify/uploads_spec.rb b/spec/lib/gitlab/verify/uploads_spec.rb
new file mode 100644
index 00000000000..6146ce61226
--- /dev/null
+++ b/spec/lib/gitlab/verify/uploads_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe Gitlab::Verify::Uploads do
+ include GitlabVerifyHelpers
+
+ it_behaves_like 'Gitlab::Verify::BatchVerifier subclass' do
+ let(:projects) { create_list(:project, 3, :with_avatar) }
+ let!(:objects) { projects.flat_map(&:uploads) }
+ end
+
+ describe '#run_batches' do
+ let(:project) { create(:project, :with_avatar) }
+ let(:failures) { collect_failures }
+ let(:failure) { failures[upload] }
+
+ let!(:upload) { project.uploads.first }
+
+ it 'passes uploads with the correct file' do
+ expect(failures).to eq({})
+ end
+
+ it 'fails uploads with a missing file' do
+ FileUtils.rm_f(upload.absolute_path)
+
+ expect(failures.keys).to contain_exactly(upload)
+ expect(failure).to be_a(Errno::ENOENT)
+ expect(failure.to_s).to include(upload.absolute_path)
+ end
+
+ it 'fails uploads with a mismatched checksum' do
+ upload.update!(checksum: 'something incorrect')
+
+ expect(failures.keys).to contain_exactly(upload)
+ expect(failure.to_s).to include('Checksum mismatch')
+ end
+
+ it 'fails uploads with a missing precalculated checksum' do
+ upload.update!(checksum: '')
+
+ expect(failures.keys).to contain_exactly(upload)
+ expect(failure.to_s).to include('Checksum missing')
+ end
+ end
+end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index bcbb9287199..83c33797bbc 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -457,7 +457,7 @@ describe Notify do
it 'has the correct subject and body' do
is_expected.to have_subject("#{project.name} | Project was moved")
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text(project.ssh_url_to_repo)
end
end
@@ -483,8 +483,8 @@ describe Notify do
to_emails = subject.header[:to].addrs.map(&:address)
expect(to_emails).to eq([recipient.notification_email])
- is_expected.to have_subject "Request to join the #{project.name_with_namespace} project"
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_subject "Request to join the #{project.full_name} project"
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project_project_members_url(project)
is_expected.to have_body_text project_member.human_access
end
@@ -503,8 +503,8 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do
- is_expected.to have_subject "Access to the #{project.name_with_namespace} project was denied"
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_subject "Access to the #{project.full_name} project was denied"
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project.web_url
end
end
@@ -520,8 +520,8 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do
- is_expected.to have_subject "Access to the #{project.name_with_namespace} project was granted"
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_subject "Access to the #{project.full_name} project was granted"
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.human_access
end
@@ -550,8 +550,8 @@ describe Notify do
it_behaves_like "a user cannot unsubscribe through footer link"
it 'contains all the useful information' do
- is_expected.to have_subject "Invitation to join the #{project.name_with_namespace} project"
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_subject "Invitation to join the #{project.full_name} project"
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.human_access
is_expected.to have_body_text project_member.invite_token
@@ -575,7 +575,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation accepted'
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.invite_email
is_expected.to have_html_escaped_body_text invited_user.name
@@ -598,7 +598,7 @@ describe Notify do
it 'contains all the useful information' do
is_expected.to have_subject 'Invitation declined'
- is_expected.to have_html_escaped_body_text project.name_with_namespace
+ is_expected.to have_html_escaped_body_text project.full_name
is_expected.to have_body_text project.web_url
is_expected.to have_body_text project_member.invite_email
end
diff --git a/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
new file mode 100644
index 00000000000..c18ae3b76d3
--- /dev/null
+++ b/spec/migrations/migrate_create_trace_artifact_sidekiq_queue_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20180306074045_migrate_create_trace_artifact_sidekiq_queue.rb')
+
+describe MigrateCreateTraceArtifactSidekiqQueue, :sidekiq, :redis do
+ include Gitlab::Database::MigrationHelpers
+
+ context 'when there are jobs in the queues' do
+ it 'correctly migrates queue when migrating up' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'pipeline_default:create_trace_artifact').perform_async('Something', [1])
+ stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+
+ described_class.new.up
+
+ expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 0
+ expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 2
+ end
+ end
+
+ it 'does not affect other queues under the same namespace' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'pipeline_default:build_coverage').perform_async('Something', [1])
+ stubbed_worker(queue: 'pipeline_default:build_trace_sections').perform_async('Something', [1])
+ stubbed_worker(queue: 'pipeline_default:pipeline_metrics').perform_async('Something', [1])
+ stubbed_worker(queue: 'pipeline_default:pipeline_notification').perform_async('Something', [1])
+ stubbed_worker(queue: 'pipeline_default:update_head_pipeline_for_merge_request').perform_async('Something', [1])
+
+ described_class.new.up
+
+ expect(sidekiq_queue_length('pipeline_default:build_coverage')).to eq 1
+ expect(sidekiq_queue_length('pipeline_default:build_trace_sections')).to eq 1
+ expect(sidekiq_queue_length('pipeline_default:pipeline_metrics')).to eq 1
+ expect(sidekiq_queue_length('pipeline_default:pipeline_notification')).to eq 1
+ expect(sidekiq_queue_length('pipeline_default:update_head_pipeline_for_merge_request')).to eq 1
+ end
+ end
+
+ it 'correctly migrates queue when migrating down' do
+ Sidekiq::Testing.disable! do
+ stubbed_worker(queue: 'pipeline_background:archive_trace').perform_async('Something', [1])
+
+ described_class.new.down
+
+ expect(sidekiq_queue_length('pipeline_default:create_trace_artifact')).to eq 1
+ expect(sidekiq_queue_length('pipeline_background:archive_trace')).to eq 0
+ end
+ end
+ end
+
+ context 'when there are no jobs in the queues' do
+ it 'does not raise error when migrating up' do
+ expect { described_class.new.up }.not_to raise_error
+ end
+
+ it 'does not raise error when migrating down' do
+ expect { described_class.new.down }.not_to raise_error
+ end
+ end
+
+ def stubbed_worker(queue:)
+ Class.new do
+ include Sidekiq::Worker
+ sidekiq_options queue: queue
+ end
+ end
+end
diff --git a/spec/models/badge_spec.rb b/spec/models/badge_spec.rb
new file mode 100644
index 00000000000..33dc19e3432
--- /dev/null
+++ b/spec/models/badge_spec.rb
@@ -0,0 +1,94 @@
+require 'spec_helper'
+
+describe Badge do
+ let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+ describe 'validations' do
+ # Requires the let variable url_sym
+ shared_examples 'placeholder url' do
+ let(:badge) { build(:badge) }
+
+ it 'allows url with http protocol' do
+ badge[url_sym] = 'http://www.example.com'
+
+ expect(badge).to be_valid
+ end
+
+ it 'allows url with https protocol' do
+ badge[url_sym] = 'https://www.example.com'
+
+ expect(badge).to be_valid
+ end
+
+ it 'cannot be empty' do
+ badge[url_sym] = ''
+
+ expect(badge).not_to be_valid
+ end
+
+ it 'cannot be nil' do
+ badge[url_sym] = nil
+
+ expect(badge).not_to be_valid
+ end
+
+ it 'accept badges placeholders' do
+ badge[url_sym] = placeholder_url
+
+ expect(badge).to be_valid
+ end
+
+ it 'sanitize url' do
+ badge[url_sym] = 'javascript:alert(1)'
+
+ expect(badge).not_to be_valid
+ end
+ end
+
+ context 'link_url format' do
+ let(:url_sym) { :link_url }
+
+ it_behaves_like 'placeholder url'
+ end
+
+ context 'image_url format' do
+ let(:url_sym) { :image_url }
+
+ it_behaves_like 'placeholder url'
+ end
+ end
+
+ shared_examples 'rendered_links' do
+ it 'should use the project information to populate the url placeholders' do
+ stub_project_commit_info(project)
+
+ expect(badge.public_send("rendered_#{method}", project)).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
+ end
+
+ it 'returns the url if the project used is nil' do
+ expect(badge.public_send("rendered_#{method}", nil)).to eq placeholder_url
+ end
+
+ def stub_project_commit_info(project)
+ allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever'))
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+ end
+
+ context 'methods' do
+ let(:badge) { build(:badge, link_url: placeholder_url, image_url: placeholder_url) }
+ let!(:project) { create(:project) }
+
+ context '#rendered_link_url' do
+ let(:method) { :link_url }
+
+ it_behaves_like 'rendered_links'
+ end
+
+ context '#rendered_image_url' do
+ let(:method) { :image_url }
+
+ it_behaves_like 'rendered_links'
+ end
+ end
+end
diff --git a/spec/models/badges/group_badge_spec.rb b/spec/models/badges/group_badge_spec.rb
new file mode 100644
index 00000000000..ed7f83d0489
--- /dev/null
+++ b/spec/models/badges/group_badge_spec.rb
@@ -0,0 +1,11 @@
+require 'spec_helper'
+
+describe GroupBadge do
+ describe 'associations' do
+ it { is_expected.to belong_to(:group) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:group) }
+ end
+end
diff --git a/spec/models/badges/project_badge_spec.rb b/spec/models/badges/project_badge_spec.rb
new file mode 100644
index 00000000000..0e1a8159cb6
--- /dev/null
+++ b/spec/models/badges/project_badge_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe ProjectBadge do
+ let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ end
+
+ describe 'validations' do
+ it { is_expected.to validate_presence_of(:project) }
+ end
+
+ shared_examples 'rendered_links' do
+ it 'should use the badge project information to populate the url placeholders' do
+ stub_project_commit_info(project)
+
+ expect(badge.public_send("rendered_#{method}")).to eq "http://www.example.com/#{project.full_path}/#{project.id}/master/whatever"
+ end
+
+ def stub_project_commit_info(project)
+ allow(project).to receive(:commit).and_return(double('Commit', sha: 'whatever'))
+ allow(project).to receive(:default_branch).and_return('master')
+ end
+ end
+
+ context 'methods' do
+ let(:badge) { build(:project_badge, link_url: placeholder_url, image_url: placeholder_url) }
+ let!(:project) { badge.project }
+
+ context '#rendered_link_url' do
+ let(:method) { :link_url }
+
+ it_behaves_like 'rendered_links'
+ end
+
+ context '#rendered_image_url' do
+ let(:method) { :image_url }
+
+ it_behaves_like 'rendered_links'
+ end
+ end
+end
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 612a3c8e413..a574779e39d 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -34,6 +34,8 @@ describe Clusters::Applications::Runner do
is_expected.to include('checkInterval')
is_expected.to include('rbac')
is_expected.to include('runners')
+ is_expected.to include('privileged: true')
+ is_expected.to include('image: ubuntu:16.04')
is_expected.to include('resources')
is_expected.to include("runnerToken: #{ci_runner.token}")
is_expected.to include("gitlabUrl: #{Gitlab::Routing.url_helpers.root_url}")
@@ -65,5 +67,33 @@ describe Clusters::Applications::Runner do
expect(gitlab_runner.runner).not_to be_nil
end
end
+
+ context 'with duplicated values on vendor/runner/values.yaml' do
+ let(:values) do
+ {
+ "concurrent" => 4,
+ "checkInterval" => 3,
+ "rbac" => {
+ "create" => false
+ },
+ "clusterWideAccess" => false,
+ "runners" => {
+ "privileged" => false,
+ "image" => "ubuntu:16.04",
+ "builds" => {},
+ "services" => {},
+ "helpers" => {}
+ }
+ }
+ end
+
+ before do
+ allow(gitlab_runner).to receive(:chart_values).and_return(values)
+ end
+
+ it 'should overwrite values.yaml' do
+ is_expected.to include("privileged: #{gitlab_runner.privileged}")
+ end
+ end
end
end
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 4f16b73ef38..abfc0896a41 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -18,6 +18,7 @@ describe Group do
it { is_expected.to have_many(:uploads).dependent(:destroy) }
it { is_expected.to have_one(:chat_team) }
it { is_expected.to have_many(:custom_attributes).class_name('GroupCustomAttribute') }
+ it { is_expected.to have_many(:badges).class_name('GroupBadge') }
describe '#members & #requesters' do
let(:requester) { create(:user) }
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 04440d890aa..e66109fd98f 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -47,7 +47,7 @@ describe AsanaService do
it 'calls Asana service to create a story' do
data = create_data_for_commits('Message from commit. related to #123456')
- expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
+ expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.full_name} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
d1 = double('Asana::Task')
expect(d1).to receive(:add_comment).with(text: expected_message)
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index 23db29cb541..3e2a166cdd6 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -29,7 +29,7 @@ describe HipchatService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' }
- let(:project_name) { project.name_with_namespace.gsub(/\s/, '') }
+ let(:project_name) { project.full_name.gsub(/\s/, '') }
let(:token) { 'verySecret' }
let(:server_url) { 'https://hipchat.example.com'}
let(:push_sample_data) do
@@ -303,7 +303,7 @@ describe HipchatService do
message = hipchat.__send__(:create_pipeline_message, data)
project_url = project.web_url
- project_name = project.name_with_namespace.gsub(/\s/, '')
+ project_name = project.full_name.gsub(/\s/, '')
pipeline_attributes = data[:object_attributes]
ref = pipeline_attributes[:ref]
ref_type = pipeline_attributes[:tag] ? 'tag' : 'branch'
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 748c366efca..54ef0be67ff 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -166,7 +166,6 @@ describe JiraService do
# Creates comment
expect(WebMock).to have_requested(:post, @comment_url)
-
# Creates Remote Link in JIRA issue fields
expect(WebMock).to have_requested(:post, @remote_link_url).with(
body: hash_including(
@@ -174,7 +173,7 @@ describe JiraService do
object: {
url: "#{Gitlab.config.gitlab.url}/#{project.full_path}/commit/#{merge_request.diff_head_sha}",
title: "GitLab: Solved by commit #{merge_request.diff_head_sha}.",
- icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+ icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
status: { resolved: true }
}
)
diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
index 522cf15f3ba..a5bdf9a9337 100644
--- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb
+++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb
@@ -31,10 +31,10 @@ describe MattermostSlashCommandsService do
url: 'http://trigger.url',
icon_url: 'http://icon.url/icon.png',
auto_complete: true,
- auto_complete_desc: "Perform common operations on: #{project.name_with_namespace}",
+ auto_complete_desc: "Perform common operations on: #{project.full_name}",
auto_complete_hint: '[help]',
- description: "Perform common operations on: #{project.name_with_namespace}",
- display_name: "GitLab / #{project.name_with_namespace}",
+ description: "Perform common operations on: #{project.full_name}",
+ display_name: "GitLab / #{project.full_name}",
method: 'P',
username: 'GitLab'
}.to_json)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index f4faec9e52a..b1c9e6754b9 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -80,6 +80,7 @@ describe Project do
it { is_expected.to have_many(:members_and_requesters) }
it { is_expected.to have_many(:clusters) }
it { is_expected.to have_many(:custom_attributes).class_name('ProjectCustomAttribute') }
+ it { is_expected.to have_many(:project_badges).class_name('ProjectBadge') }
it { is_expected.to have_many(:lfs_file_locks) }
context 'after initialized' do
@@ -517,6 +518,20 @@ describe Project do
it 'returns the project\'s last update date if it has no events' do
expect(project.last_activity_date).to eq(project.updated_at)
end
+
+ it 'returns the most recent timestamp' do
+ project.update_attributes(updated_at: nil,
+ last_activity_at: timestamp,
+ last_repository_updated_at: timestamp - 1.hour)
+
+ expect(project.last_activity_date).to eq(timestamp)
+
+ project.update_attributes(updated_at: timestamp,
+ last_activity_at: timestamp - 1.hour,
+ last_repository_updated_at: nil)
+
+ expect(project.last_activity_date).to eq(timestamp)
+ end
end
end
@@ -3331,4 +3346,36 @@ describe Project do
end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
end
end
+
+ describe '#badges' do
+ let(:project_group) { create(:group) }
+ let(:project) { create(:project, path: 'avatar', namespace: project_group) }
+
+ before do
+ create_list(:project_badge, 2, project: project)
+ create(:group_badge, group: project_group)
+ end
+
+ it 'returns the project and the project group badges' do
+ create(:group_badge, group: create(:group))
+
+ expect(Badge.count).to eq 4
+ expect(project.badges.count).to eq 3
+ end
+
+ if Group.supports_nested_groups?
+ context 'with nested_groups' do
+ let(:parent_group) { create(:group) }
+
+ before do
+ create_list(:group_badge, 2, group: project_group)
+ project_group.update(parent: parent_group)
+ end
+
+ it 'returns the project and the project nested groups badges' do
+ expect(project.badges.count).to eq 5
+ end
+ end
+ end
+ end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 1e7671476f1..8b4b5873704 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -14,13 +14,13 @@ describe ProjectWiki do
it { is_expected.to delegate_method(:repository_storage_path).to :project }
it { is_expected.to delegate_method(:hashed_storage?).to :project }
- describe "#path_with_namespace" do
+ describe "#full_path" do
it "returns the project path with namespace with the .wiki extension" do
- expect(subject.path_with_namespace).to eq(project.full_path + '.wiki')
+ expect(subject.full_path).to eq(project.full_path + '.wiki')
end
it 'returns the same value as #full_path' do
- expect(subject.path_with_namespace).to eq(subject.full_path)
+ expect(subject.full_path).to eq(subject.full_path)
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3531de244bd..00b5226d874 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1635,6 +1635,32 @@ describe User do
end
end
+ describe '#authorizations_for_projects' do
+ let!(:user) { create(:user) }
+ subject { Project.where("EXISTS (?)", user.authorizations_for_projects) }
+
+ it 'includes projects that belong to a user, but no other projects' do
+ owned = create(:project, :private, namespace: user.namespace)
+ member = create(:project, :private).tap { |p| p.add_master(user) }
+ other = create(:project)
+
+ expect(subject).to include(owned)
+ expect(subject).to include(member)
+ expect(subject).not_to include(other)
+ end
+
+ it 'includes projects a user has access to, but no other projects' do
+ other_user = create(:user)
+ accessible = create(:project, :private, namespace: other_user.namespace) do |project|
+ project.add_developer(user)
+ end
+ other = create(:project)
+
+ expect(subject).to include(accessible)
+ expect(subject).not_to include(other)
+ end
+ end
+
describe '#authorized_projects', :delete do
context 'with a minimum access level' do
it 'includes projects for which the user is an owner' do
diff --git a/spec/requests/api/badges_spec.rb b/spec/requests/api/badges_spec.rb
new file mode 100644
index 00000000000..ae64a9ca162
--- /dev/null
+++ b/spec/requests/api/badges_spec.rb
@@ -0,0 +1,367 @@
+require 'spec_helper'
+
+describe API::Badges do
+ let(:master) { create(:user, username: 'master_user') }
+ let(:developer) { create(:user) }
+ let(:access_requester) { create(:user) }
+ let(:stranger) { create(:user) }
+ let(:project_group) { create(:group) }
+ let(:project) { setup_project }
+ let!(:group) { setup_group }
+
+ shared_context 'source helpers' do
+ def get_source(source_type)
+ source_type == 'project' ? project : group
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/badges' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges", stranger) }
+ end
+
+ %i[master developer access_requester stranger].each do |type|
+ context "when authenticated as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+ badges_count = source_type == 'project' ? 3 : 2
+
+ get api("/#{source_type.pluralize}/#{source.id}/badges", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(badges_count)
+ end
+ end
+ end
+
+ it 'avoids N+1 queries' do
+ # Establish baseline
+ get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+
+ control = ActiveRecord::QueryRecorder.new do
+ get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+ end
+
+ project.add_developer(create(:user))
+
+ expect do
+ get api("/#{source_type.pluralize}/#{source.id}/badges", master)
+ end.not_to exceed_query_limit(control)
+ end
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/badges/:badge_id' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { get api("/#{source_type.pluralize}/#{source.id}/badges/#{developer.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member' do
+ %i[master developer access_requester stranger].each do |type|
+ let(:badge) { source.badges.first }
+
+ context "as a #{type}" do
+ it 'returns 200' do
+ user = public_send(type)
+
+ get api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['id']).to eq(badge.id)
+ expect(json_response['link_url']).to eq(badge.link_url)
+ expect(json_response['rendered_link_url']).to eq(badge.rendered_link_url)
+ expect(json_response['image_url']).to eq(badge.image_url)
+ expect(json_response['rendered_image_url']).to eq(badge.rendered_image_url)
+ expect(json_response['kind']).to eq source_type
+ end
+ end
+ end
+ end
+ end
+ end
+
+ shared_examples 'POST /:sources/:id/badges' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+ let(:example_url) { 'http://www.example.com' }
+ let(:example_url2) { 'http://www.example1.com' }
+
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ post api("/#{source_type.pluralize}/#{source.id}/badges", stranger),
+ link_url: example_url, image_url: example_url2
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+
+ post api("/#{source_type.pluralize}/#{source.id}/badges", user),
+ link_url: example_url, image_url: example_url2
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'creates a new badge' do
+ expect do
+ post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+ link_url: example_url, image_url: example_url2
+
+ expect(response).to have_gitlab_http_status(201)
+ end.to change { source.badges.count }.by(1)
+
+ expect(json_response['link_url']).to eq(example_url)
+ expect(json_response['image_url']).to eq(example_url2)
+ expect(json_response['kind']).to eq source_type
+ end
+ end
+
+ it 'returns 400 when link_url is not given' do
+ post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+ link_url: example_url
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 400 when image_url is not given' do
+ post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+ image_url: example_url2
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 400 when link_url or image_url is not valid' do
+ post api("/#{source_type.pluralize}/#{source.id}/badges", master),
+ link_url: 'whatever', image_url: 'whatever'
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
+ shared_examples 'PUT /:sources/:id/badges/:badge_id' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+
+ context "with :sources == #{source_type.pluralize}" do
+ let(:badge) { source.badges.first }
+ let(:example_url) { 'http://www.example.com' }
+ let(:example_url2) { 'http://www.example1.com' }
+
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger),
+ link_url: example_url
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+
+ put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user),
+ link_url: example_url
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'updates the member' do
+ put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master),
+ link_url: example_url, image_url: example_url2
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['link_url']).to eq(example_url)
+ expect(json_response['image_url']).to eq(example_url2)
+ expect(json_response['kind']).to eq source_type
+ end
+ end
+
+ it 'returns 400 when link_url or image_url is not valid' do
+ put api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master),
+ link_url: 'whatever', image_url: 'whatever'
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
+ shared_examples 'DELETE /:sources/:id/badges/:badge_id' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+
+ context "with :sources == #{source_type.pluralize}" do
+ let(:badge) { source.badges.first }
+
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) { delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", stranger) }
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester developer stranger].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+
+ delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'deletes the badge' do
+ expect do
+ delete api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master)
+
+ expect(response).to have_gitlab_http_status(204)
+ end.to change { source.badges.count }.by(-1)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/#{source_type.pluralize}/#{source.id}/badges/#{badge.id}", master) }
+ end
+ end
+
+ it 'returns 404 if badge does not exist' do
+ delete api("/#{source_type.pluralize}/#{source.id}/badges/123", master)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ shared_examples 'GET /:sources/:id/badges/render' do |source_type|
+ include_context 'source helpers'
+
+ let(:source) { get_source(source_type) }
+ let(:example_url) { 'http://www.example.com' }
+ let(:example_url2) { 'http://www.example1.com' }
+
+ context "with :sources == #{source_type.pluralize}" do
+ it_behaves_like 'a 404 response when source is private' do
+ let(:route) do
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", stranger)
+ end
+ end
+
+ context 'when authenticated as a non-member or member with insufficient rights' do
+ %i[access_requester stranger developer].each do |type|
+ context "as a #{type}" do
+ it 'returns 403' do
+ user = public_send(type)
+
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", user)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+ end
+
+ context 'when authenticated as a master/owner' do
+ it 'gets the rendered badge values' do
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}&image_url=#{example_url2}", master)
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(json_response.keys).to contain_exactly('link_url', 'rendered_link_url', 'image_url', 'rendered_image_url')
+ expect(json_response['link_url']).to eq(example_url)
+ expect(json_response['image_url']).to eq(example_url2)
+ expect(json_response['rendered_link_url']).to eq(example_url)
+ expect(json_response['rendered_image_url']).to eq(example_url2)
+ end
+ end
+
+ it 'returns 400 when link_url is not given' do
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=#{example_url}", master)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 400 when image_url is not given' do
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?image_url=#{example_url}", master)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 400 when link_url or image_url is not valid' do
+ get api("/#{source_type.pluralize}/#{source.id}/badges/render?link_url=whatever&image_url=whatever", master)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
+ context 'when deleting a badge' do
+ context 'and the source is a project' do
+ it 'cannot delete badges owned by the project group' do
+ delete api("/projects/#{project.id}/badges/#{project_group.badges.first.id}", master)
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
+ describe 'Endpoints' do
+ %w(project group).each do |source_type|
+ it_behaves_like 'GET /:sources/:id/badges', source_type
+ it_behaves_like 'GET /:sources/:id/badges/:badge_id', source_type
+ it_behaves_like 'GET /:sources/:id/badges/render', source_type
+ it_behaves_like 'POST /:sources/:id/badges', source_type
+ it_behaves_like 'PUT /:sources/:id/badges/:badge_id', source_type
+ it_behaves_like 'DELETE /:sources/:id/badges/:badge_id', source_type
+ end
+ end
+
+ def setup_project
+ create(:project, :public, :access_requestable, creator_id: master.id, namespace: project_group) do |project|
+ project.add_developer(developer)
+ project.add_master(master)
+ project.request_access(access_requester)
+ project.project_badges << build(:project_badge, project: project)
+ project.project_badges << build(:project_badge, project: project)
+ project_group.badges << build(:group_badge, group: group)
+ end
+ end
+
+ def setup_group
+ create(:group, :public, :access_requestable) do |group|
+ group.add_developer(developer)
+ group.add_owner(master)
+ group.request_access(access_requester)
+ group.badges << build(:group_badge, group: group)
+ group.badges << build(:group_badge, group: group)
+ end
+ end
+end
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index e433597f58b..64f51d9843d 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -39,6 +39,27 @@ describe API::Branches do
end
end
+ context 'when search parameter is passed' do
+ context 'and branch exists' do
+ it 'returns correct branches' do
+ get api(route, user), per_page: 100, search: branch_name
+
+ searched_branch_names = json_response.map { |branch| branch['name'] }
+ project_branch_names = project.repository.branch_names.grep(/#{branch_name}/)
+
+ expect(searched_branch_names).to match_array(project_branch_names)
+ end
+ end
+
+ context 'and branch does not exist' do
+ it 'returns an empty array' do
+ get api(route, user), per_page: 100, search: 'no_such_branch_name_entropy_of_jabadabadu'
+
+ expect(json_response).to eq []
+ end
+ end
+ end
+
context 'when unauthenticated', 'and project is public' do
before do
project.update(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index d1569e5d650..6614e8cea43 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -163,6 +163,42 @@ describe API::Issues do
expect(first_issue['id']).to eq(issue.id)
end
+ context 'filtering before a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: Date.new(2000, 1, 1), updated_at: Date.new(2000, 1, 1)) }
+
+ it 'returns issues created before a specific date' do
+ get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues updated before a specific date' do
+ get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+ end
+
+ context 'filtering after a specific date' do
+ let!(:issue2) { create(:issue, project: project, author: user, created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+
+ it 'returns issues created after a specific date' do
+ get api("/issues?created_after=#{issue2.created_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+
+ it 'returns issues updated after a specific date' do
+ get api("/issues?updated_after=#{issue2.updated_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(first_issue['id']).to eq(issue2.id)
+ end
+ end
+
it 'returns an array of labeled issues' do
get api("/issues", user), labels: label.title
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index e8eb01f6c32..484322752c0 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -172,6 +172,42 @@ describe API::MergeRequests do
end
end
+ it 'returns merge requests created before a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: Date.new(2000, 1, 1))
+
+ get api('/merge_requests?created_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests created after a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', created_at: 1.week.from_now)
+
+ get api("/merge_requests?created_after=#{merge_request2.created_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests updated before a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: Date.new(2000, 1, 1))
+
+ get api('/merge_requests?updated_before=2000-01-02T00:00:00.060Z', user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
+ it 'returns merge requests updated after a specific date' do
+ merge_request2 = create(:merge_request, :simple, source_project: project, target_project: project, source_branch: 'feature_1', updated_at: 1.week.from_now)
+
+ get api("/merge_requests?updated_after=#{merge_request2.updated_at}", user)
+
+ expect(json_response.size).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request2.id)
+ end
+
context 'search params' do
before do
merge_request.update(title: 'Search title', description: 'Search description')
diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb
index 025165622b7..dc3a116c060 100644
--- a/spec/requests/api/pages_domains_spec.rb
+++ b/spec/requests/api/pages_domains_spec.rb
@@ -16,7 +16,7 @@ describe API::PagesDomains do
let(:route) { "/projects/#{project.id}/pages/domains" }
let(:route_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain.domain}" }
- let(:route_domain_path) { "/projects/#{project.path_with_namespace.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" }
+ let(:route_domain_path) { "/projects/#{project.full_path.gsub('/', '%2F')}/pages/domains/#{pages_domain.domain}" }
let(:route_secure_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_secure.domain}" }
let(:route_expired_domain) { "/projects/#{project.id}/pages/domains/#{pages_domain_expired.domain}" }
let(:route_vacant_domain) { "/projects/#{project.id}/pages/domains/www.vacant-domain.test" }
diff --git a/spec/requests/api/project_export_spec.rb b/spec/requests/api/project_export_spec.rb
new file mode 100644
index 00000000000..fbed527963f
--- /dev/null
+++ b/spec/requests/api/project_export_spec.rb
@@ -0,0 +1,290 @@
+require 'spec_helper'
+
+describe API::ProjectExport do
+ set(:project) { create(:project) }
+ set(:project_none) { create(:project) }
+ set(:project_started) { create(:project) }
+ set(:project_finished) { create(:project) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:admin) }
+
+ let(:path) { "/projects/#{project.id}/export" }
+ let(:path_none) { "/projects/#{project_none.id}/export" }
+ let(:path_started) { "/projects/#{project_started.id}/export" }
+ let(:path_finished) { "/projects/#{project_finished.id}/export" }
+
+ let(:download_path) { "/projects/#{project.id}/export/download" }
+ let(:download_path_none) { "/projects/#{project_none.id}/export/download" }
+ let(:download_path_started) { "/projects/#{project_started.id}/export/download" }
+ let(:download_path_finished) { "/projects/#{project_finished.id}/export/download" }
+
+ let(:export_path) { "#{Dir.tmpdir}/project_export_spec" }
+
+ before do
+ allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path)
+
+ # simulate exporting work directory
+ FileUtils.mkdir_p File.join(project_started.export_path, 'securerandom-hex')
+
+ # simulate exported
+ FileUtils.mkdir_p project_finished.export_path
+ FileUtils.touch File.join(project_finished.export_path, '_export.tar.gz')
+ end
+
+ after do
+ FileUtils.rm_rf(export_path, secure: true)
+ end
+
+ shared_examples_for 'when project export is disabled' do
+ before do
+ stub_application_setting(project_export_enabled?: false)
+ end
+
+ it_behaves_like '404 response'
+ end
+
+ describe 'GET /projects/:project_id/export' do
+ shared_examples_for 'get project export status not found' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(path, user) }
+ end
+ end
+
+ shared_examples_for 'get project export status denied' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(path, user) }
+ end
+ end
+
+ shared_examples_for 'get project export status ok' do
+ it 'is none' do
+ get api(path_none, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('none')
+ end
+
+ it 'is started' do
+ get api(path_started, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('started')
+ end
+
+ it 'is finished' do
+ get api(path_finished, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/project/export_status')
+ expect(json_response['export_status']).to eq('finished')
+ end
+ end
+
+ it_behaves_like 'when project export is disabled' do
+ let(:request) { get api(path, admin) }
+ end
+
+ context 'when project export is enabled' do
+ context 'when user is an admin' do
+ let(:user) { admin }
+
+ it_behaves_like 'get project export status ok'
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ project_none.add_master(user)
+ project_started.add_master(user)
+ project_finished.add_master(user)
+ end
+
+ it_behaves_like 'get project export status ok'
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'get project export status denied'
+ end
+
+ context 'when user is a reporter' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it_behaves_like 'get project export status denied'
+ end
+
+ context 'when user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ it_behaves_like 'get project export status denied'
+ end
+
+ context 'when user is not a member' do
+ it_behaves_like 'get project export status not found'
+ end
+ end
+ end
+
+ describe 'GET /projects/:project_id/export/download' do
+ shared_examples_for 'get project export download not found' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(download_path, user) }
+ end
+ end
+
+ shared_examples_for 'get project export download denied' do
+ it_behaves_like '403 response' do
+ let(:request) { get api(download_path, user) }
+ end
+ end
+
+ shared_examples_for 'get project export download' do
+ it_behaves_like '404 response' do
+ let(:request) { get api(download_path_none, user) }
+ end
+
+ it_behaves_like '404 response' do
+ let(:request) { get api(download_path_started, user) }
+ end
+
+ it 'downloads' do
+ get api(download_path_finished, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+ end
+
+ it_behaves_like 'when project export is disabled' do
+ let(:request) { get api(download_path, admin) }
+ end
+
+ context 'when project export is enabled' do
+ context 'when user is an admin' do
+ let(:user) { admin }
+
+ it_behaves_like 'get project export download'
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ project_none.add_master(user)
+ project_started.add_master(user)
+ project_finished.add_master(user)
+ end
+
+ it_behaves_like 'get project export download'
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'get project export download denied'
+ end
+
+ context 'when user is a reporter' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it_behaves_like 'get project export download denied'
+ end
+
+ context 'when user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ it_behaves_like 'get project export download denied'
+ end
+
+ context 'when user is not a member' do
+ it_behaves_like 'get project export download not found'
+ end
+ end
+ end
+
+ describe 'POST /projects/:project_id/export' do
+ shared_examples_for 'post project export start not found' do
+ it_behaves_like '404 response' do
+ let(:request) { post api(path, user) }
+ end
+ end
+
+ shared_examples_for 'post project export start denied' do
+ it_behaves_like '403 response' do
+ let(:request) { post api(path, user) }
+ end
+ end
+
+ shared_examples_for 'post project export start' do
+ it 'starts' do
+ post api(path, user)
+
+ expect(response).to have_gitlab_http_status(202)
+ end
+ end
+
+ it_behaves_like 'when project export is disabled' do
+ let(:request) { post api(path, admin) }
+ end
+
+ context 'when project export is enabled' do
+ context 'when user is an admin' do
+ let(:user) { admin }
+
+ it_behaves_like 'post project export start'
+ end
+
+ context 'when user is a master' do
+ before do
+ project.add_master(user)
+ project_none.add_master(user)
+ project_started.add_master(user)
+ project_finished.add_master(user)
+ end
+
+ it_behaves_like 'post project export start'
+ end
+
+ context 'when user is a developer' do
+ before do
+ project.add_developer(user)
+ end
+
+ it_behaves_like 'post project export start denied'
+ end
+
+ context 'when user is a reporter' do
+ before do
+ project.add_reporter(user)
+ end
+
+ it_behaves_like 'post project export start denied'
+ end
+
+ context 'when user is a guest' do
+ before do
+ project.add_guest(user)
+ end
+
+ it_behaves_like 'post project export start denied'
+ end
+
+ context 'when user is not a member' do
+ it_behaves_like 'post project export start not found'
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 72cafac3f90..95c23726a79 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -698,10 +698,10 @@ describe API::Runner do
end
end
- context 'when tace is given' do
+ context 'when trace is given' do
it 'creates a trace artifact' do
allow(BuildFinishedWorker).to receive(:perform_async).with(job.id) do
- CreateTraceArtifactWorker.new.perform(job.id)
+ ArchiveTraceWorker.new.perform(job.id)
end
update_job(state: 'success', trace: 'BUILD TRACE UPDATED')
@@ -1100,11 +1100,13 @@ describe API::Runner do
context 'posts artifacts file and metadata file' do
let!(:artifacts) { file_upload }
+ let!(:artifacts_sha256) { Digest::SHA256.file(artifacts.path).hexdigest }
let!(:metadata) { file_upload2 }
let(:stored_artifacts_file) { job.reload.artifacts_file.file }
let(:stored_metadata_file) { job.reload.artifacts_metadata.file }
let(:stored_artifacts_size) { job.reload.artifacts_size }
+ let(:stored_artifacts_sha256) { job.reload.job_artifacts_archive.file_sha256 }
before do
post(api("/jobs/#{job.id}/artifacts"), post_data, headers_with_token)
@@ -1114,6 +1116,7 @@ describe API::Runner do
let(:post_data) do
{ 'file.path' => artifacts.path,
'file.name' => artifacts.original_filename,
+ 'file.sha256' => artifacts_sha256,
'metadata.path' => metadata.path,
'metadata.name' => metadata.original_filename }
end
@@ -1123,6 +1126,7 @@ describe API::Runner do
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
expect(stored_artifacts_size).to eq(72821)
+ expect(stored_artifacts_sha256).to eq(artifacts_sha256)
end
end
diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb
index 0467e0251b3..6dbbb1ad7bb 100644
--- a/spec/requests/git_http_spec.rb
+++ b/spec/requests/git_http_spec.rb
@@ -795,9 +795,9 @@ describe 'Git HTTP requests' do
let(:path) { 'doesnt/exist.git' }
before do
- allow(Gitlab::Auth::LDAP::Config).to receive(:enabled?).and_return(true)
- allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
- allow(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
+ allow(Gitlab::Auth::OAuth::Provider).to receive(:enabled?).and_return(true)
+ allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).and_return(nil)
+ allow_any_instance_of(Gitlab::Auth::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
end
it_behaves_like 'pulls require Basic HTTP Authentication'
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 9128280eb5a..290eeae828e 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -172,7 +172,7 @@ describe Auth::ContainerRegistryAuthenticationService do
end
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -200,7 +200,7 @@ describe Auth::ContainerRegistryAuthenticationService do
end
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -239,7 +239,7 @@ describe Auth::ContainerRegistryAuthenticationService do
end
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -270,7 +270,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow anyone to delete images' do
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -311,7 +311,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow anyone to delete images' do
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -323,7 +323,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow anyone to pull or push images' do
let(:current_user) { create(:user, external: true) }
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:pull,push" }
+ { scope: "repository:#{project.full_path}:pull,push" }
end
it_behaves_like 'an inaccessible'
@@ -333,7 +333,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow anyone to delete images' do
let(:current_user) { create(:user, external: true) }
let(:current_params) do
- { scope: "repository:#{project.path_with_namespace}:*" }
+ { scope: "repository:#{project.full_path}:*" }
end
it_behaves_like 'an inaccessible'
@@ -359,7 +359,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'allow to delete images' do
let(:current_params) do
- { scope: "repository:#{current_project.path_with_namespace}:*" }
+ { scope: "repository:#{current_project.full_path}:*" }
end
it_behaves_like 'a deletable' do
@@ -398,7 +398,7 @@ describe Auth::ContainerRegistryAuthenticationService do
context 'disallow to delete images' do
let(:current_params) do
- { scope: "repository:#{current_project.path_with_namespace}:*" }
+ { scope: "repository:#{current_project.full_path}:*" }
end
it_behaves_like 'an inaccessible' do
diff --git a/spec/services/ci/create_trace_artifact_service_spec.rb b/spec/services/ci/create_trace_artifact_service_spec.rb
deleted file mode 100644
index 8c5e8e438c7..00000000000
--- a/spec/services/ci/create_trace_artifact_service_spec.rb
+++ /dev/null
@@ -1,63 +0,0 @@
-require 'spec_helper'
-
-describe Ci::CreateTraceArtifactService do
- describe '#execute' do
- subject { described_class.new(nil, nil).execute(job) }
-
- context 'when the job does not have trace artifact' do
- context 'when the job has a trace file' do
- let!(:job) { create(:ci_build, :trace_live) }
- let!(:legacy_path) { job.trace.read { |stream| return stream.path } }
- let!(:legacy_checksum) { Digest::SHA256.file(legacy_path).hexdigest }
- let(:new_path) { job.job_artifacts_trace.file.path }
- let(:new_checksum) { Digest::SHA256.file(new_path).hexdigest }
-
- it { expect(File.exist?(legacy_path)).to be_truthy }
-
- it 'creates trace artifact' do
- expect { subject }.to change { Ci::JobArtifact.count }.by(1)
-
- expect(File.exist?(legacy_path)).to be_falsy
- expect(File.exist?(new_path)).to be_truthy
- expect(new_checksum).to eq(legacy_checksum)
- expect(job.job_artifacts_trace.file.exists?).to be_truthy
- expect(job.job_artifacts_trace.file.filename).to eq('job.log')
- end
-
- context 'when failed to create trace artifact record' do
- before do
- # When ActiveRecord error happens
- allow_any_instance_of(Ci::JobArtifact).to receive(:save).and_return(false)
- allow_any_instance_of(Ci::JobArtifact).to receive_message_chain(:errors, :full_messages)
- .and_return("Error")
-
- subject rescue nil
-
- job.reload
- end
-
- it 'keeps legacy trace and removes trace artifact' do
- expect(File.exist?(legacy_path)).to be_truthy
- expect(job.job_artifacts_trace).to be_nil
- end
- end
- end
-
- context 'when the job does not have a trace file' do
- let!(:job) { create(:ci_build) }
-
- it 'does not create trace artifact' do
- expect { subject }.not_to change { Ci::JobArtifact.count }
- end
- end
- end
-
- context 'when the job has already had trace artifact' do
- let!(:job) { create(:ci_build, :trace_artifact) }
-
- it 'does not create trace artifact' do
- expect { subject }.not_to change { Ci::JobArtifact.count }
- end
- end
- end
-end
diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb
index 5d226f34d2d..44a83c436cb 100644
--- a/spec/services/merge_requests/create_service_spec.rb
+++ b/spec/services/merge_requests/create_service_spec.rb
@@ -28,6 +28,7 @@ describe MergeRequests::CreateService do
it 'creates an MR' do
expect(merge_request).to be_valid
+ expect(merge_request.work_in_progress?).to be(false)
expect(merge_request.title).to eq('Awesome merge_request')
expect(merge_request.assignee).to be_nil
expect(merge_request.merge_params['force_remove_source_branch']).to eq('1')
@@ -62,6 +63,40 @@ describe MergeRequests::CreateService do
expect(Event.where(attributes).count).to eq(1)
end
+ describe 'when marked with /wip' do
+ context 'in title and in description' do
+ let(:opts) do
+ {
+ title: 'WIP: Awesome merge_request',
+ description: "well this is not done yet\n/wip",
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it 'sets MR to WIP' do
+ expect(merge_request.work_in_progress?).to be(true)
+ end
+ end
+
+ context 'in description only' do
+ let(:opts) do
+ {
+ title: 'Awesome merge_request',
+ description: "well this is not done yet\n/wip",
+ source_branch: 'feature',
+ target_branch: 'master',
+ assignee: assignee
+ }
+ end
+
+ it 'sets MR to WIP' do
+ expect(merge_request.work_in_progress?).to be(true)
+ end
+ end
+ end
+
context 'when merge request is assigned to someone' do
let(:opts) do
{
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 0ae26e87154..f5cff66de6d 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -57,32 +57,55 @@ describe Notes::CreateService do
end
end
- describe 'note with commands' do
- describe '/close, /label, /assign & /milestone' do
- let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
+ context 'note with commands' do
+ context 'as a user who can update the target' do
+ context '/close, /label, /assign & /milestone' do
+ let(:note_text) { %(HELLO\n/close\n/assign @#{user.username}\nWORLD) }
- it 'saves the note and does not alter the note text' do
- expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
+ it 'saves the note and does not alter the note text' do
+ expect_any_instance_of(Issues::UpdateService).to receive(:execute).and_call_original
- note = described_class.new(project, user, opts.merge(note: note_text)).execute
+ note = described_class.new(project, user, opts.merge(note: note_text)).execute
- expect(note.note).to eq "HELLO\nWORLD"
+ expect(note.note).to eq "HELLO\nWORLD"
+ end
+ end
+
+ context '/merge with sha option' do
+ let(:note_text) { %(HELLO\n/merge\nWORLD) }
+ let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+
+ it 'saves the note and exectues merge command' do
+ note = described_class.new(project, user, params).execute
+
+ expect(note.note).to eq "HELLO\nWORLD"
+ end
end
end
- describe '/merge with sha option' do
- let(:note_text) { %(HELLO\n/merge\nWORLD) }
- let(:params) { opts.merge(note: note_text, merge_request_diff_head_sha: 'sha') }
+ context 'as a user who cannot update the target' do
+ let(:note_text) { "HELLO\n/todo\n/assign #{user.to_reference}\nWORLD" }
+ let(:note) { described_class.new(project, user, opts.merge(note: note_text)).execute }
- it 'saves the note and exectues merge command' do
- note = described_class.new(project, user, params).execute
+ before do
+ project.team.find_member(user.id).update!(access_level: Gitlab::Access::GUEST)
+ end
+
+ it 'applies commands the user can execute' do
+ expect { note }.to change { user.todos_pending_count }.from(0).to(1)
+ end
+
+ it 'does not apply commands the user cannot execute' do
+ expect { note }.not_to change { issue.assignees }
+ end
+ it 'saves the note' do
expect(note.note).to eq "HELLO\nWORLD"
end
end
end
- describe 'personal snippet note' do
+ context 'personal snippet note' do
subject { described_class.new(nil, user, params).execute }
let(:snippet) { create(:personal_snippet) }
@@ -103,7 +126,7 @@ describe Notes::CreateService do
end
end
- describe 'note with emoji only' do
+ context 'note with emoji only' do
it 'creates regular note' do
opts = {
note: ':smile: ',
diff --git a/spec/services/notes/quick_actions_service_spec.rb b/spec/services/notes/quick_actions_service_spec.rb
index 5eafe56c99d..b1e218821d2 100644
--- a/spec/services/notes/quick_actions_service_spec.rb
+++ b/spec/services/notes/quick_actions_service_spec.rb
@@ -165,31 +165,17 @@ describe Notes::QuickActionsService do
let(:note) { create(:note_on_issue, project: project) }
- context 'with no current_user' do
- it 'returns false' do
- expect(described_class.supported?(note, nil)).to be_falsy
- end
- end
-
- context 'when current_user cannot update the noteable' do
- it 'returns false' do
- user = create(:user)
-
- expect(described_class.supported?(note, user)).to be_falsy
- end
- end
-
- context 'when current_user can update the noteable' do
+ context 'with a note on an issue' do
it 'returns true' do
- expect(described_class.supported?(note, master)).to be_truthy
+ expect(described_class.supported?(note)).to be_truthy
end
+ end
- context 'with a note on a commit' do
- let(:note) { create(:note_on_commit, project: project) }
+ context 'with a note on a commit' do
+ let(:note) { create(:note_on_commit, project: project) }
- it 'returns false' do
- expect(described_class.supported?(note, nil)).to be_falsy
- end
+ it 'returns false' do
+ expect(described_class.supported?(note)).to be_falsy
end
end
end
@@ -201,7 +187,7 @@ describe Notes::QuickActionsService do
service = described_class.new(project, master)
note = create(:note_on_issue, project: project)
- expect(described_class).to receive(:supported?).with(note, master)
+ expect(described_class).to receive(:supported?).with(note)
service.supported?(note)
end
diff --git a/spec/services/projects/update_pages_service_spec.rb b/spec/services/projects/update_pages_service_spec.rb
index bfb86284d86..934106627a9 100644
--- a/spec/services/projects/update_pages_service_spec.rb
+++ b/spec/services/projects/update_pages_service_spec.rb
@@ -34,6 +34,7 @@ describe Projects::UpdatePagesService do
context 'with expiry date' do
before do
build.artifacts_expire_in = "2 days"
+ build.save!
end
it "doesn't delete artifacts" do
@@ -105,6 +106,7 @@ describe Projects::UpdatePagesService do
context 'with expiry date' do
before do
build.artifacts_expire_in = "2 days"
+ build.save!
end
it "doesn't delete artifacts" do
@@ -159,6 +161,20 @@ describe Projects::UpdatePagesService do
expect(execute).not_to eq(:success)
end
+
+ context 'when timeout happens by DNS error' do
+ before do
+ allow_any_instance_of(described_class)
+ .to receive(:extract_zip_archive!).and_raise(SocketError)
+ end
+
+ it 'raises an error' do
+ expect { execute }.to raise_error(SocketError)
+
+ build.reload
+ expect(build.artifacts?).to eq(true)
+ end
+ end
end
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index c40cd5b7548..51396d34f8f 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -30,6 +30,7 @@ describe SystemHooksService do
:old_path_with_namespace
)
end
+
it do
project.old_path_with_namespace = 'transfered_from_path'
expect(event_data(project, :transfer)).to include(
@@ -45,18 +46,21 @@ describe SystemHooksService do
:owner_name, :owner_email
)
end
+
it do
expect(event_data(group, :destroy)).to include(
:event_name, :name, :created_at, :updated_at, :path, :group_id,
:owner_name, :owner_email
)
end
+
it do
expect(event_data(group_member, :create)).to include(
:event_name, :created_at, :updated_at, :group_name, :group_path,
:group_id, :user_id, :user_username, :user_name, :user_email, :group_access
)
end
+
it do
expect(event_data(group_member, :destroy)).to include(
:event_name, :created_at, :updated_at, :group_name, :group_path,
@@ -70,6 +74,14 @@ describe SystemHooksService do
expect(data[:project_visibility]).to eq('private')
end
+ it 'handles nil datetime columns' do
+ user.update_attributes(created_at: nil, updated_at: nil)
+ data = event_data(user, :destroy)
+
+ expect(data[:created_at]).to be(nil)
+ expect(data[:updated_at]).to be(nil)
+ end
+
context 'group_rename' do
it 'contains old and new path' do
allow(group).to receive(:path_was).and_return('old-path')
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 5b5edc1aa0d..a3893188c6e 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -789,7 +789,7 @@ describe SystemNoteService do
object: {
url: project_commit_url(project, commit),
title: "GitLab: Mentioned on commit - #{commit.title}",
- icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+ icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
status: { resolved: false }
}
)
@@ -815,7 +815,7 @@ describe SystemNoteService do
object: {
url: project_issue_url(project, issue),
title: "GitLab: Mentioned on issue - #{issue.title}",
- icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+ icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
status: { resolved: false }
}
)
@@ -841,7 +841,7 @@ describe SystemNoteService do
object: {
url: project_snippet_url(project, snippet),
title: "GitLab: Mentioned on snippet - #{snippet.title}",
- icon: { title: "GitLab", url16x16: "https://gitlab.com/favicon.ico" },
+ icon: { title: "GitLab", url16x16: "http://localhost/favicon.ico" },
status: { resolved: false }
}
)
diff --git a/spec/support/bare_repo_operations.rb b/spec/support/bare_repo_operations.rb
index 38d11992dc2..8eeaa37d3c5 100644
--- a/spec/support/bare_repo_operations.rb
+++ b/spec/support/bare_repo_operations.rb
@@ -11,6 +11,14 @@ class BareRepoOperations
@path_to_repo = path_to_repo
end
+ def commit_tree(tree_id, msg, parent: EMPTY_TREE_ID)
+ commit_tree_args = ['commit-tree', tree_id, '-m', msg]
+ commit_tree_args += ['-p', parent] unless parent == EMPTY_TREE_ID
+ commit_id = execute(commit_tree_args)
+
+ commit_id[0]
+ end
+
# Based on https://stackoverflow.com/a/25556917/1856239
def commit_file(file, dst_path, branch = 'master')
head_id = execute(['show', '--format=format:%H', '--no-patch', branch], allow_failure: true)[0] || EMPTY_TREE_ID
@@ -26,11 +34,9 @@ class BareRepoOperations
tree_id = execute(['write-tree'])
- commit_tree_args = ['commit-tree', tree_id[0], '-m', "Add #{dst_path}"]
- commit_tree_args += ['-p', head_id] unless head_id == EMPTY_TREE_ID
- commit_id = execute(commit_tree_args)
+ commit_id = commit_tree(tree_id[0], "Add #{dst_path}", parent: head_id)
- execute(['update-ref', "refs/heads/#{branch}", commit_id[0]])
+ execute(['update-ref', "refs/heads/#{branch}", commit_id])
end
private
diff --git a/spec/support/features/issuable_slash_commands_shared_examples.rb b/spec/support/features/issuable_slash_commands_shared_examples.rb
index 2c20821ac3f..f61469f673d 100644
--- a/spec/support/features/issuable_slash_commands_shared_examples.rb
+++ b/spec/support/features/issuable_slash_commands_shared_examples.rb
@@ -127,7 +127,6 @@ shared_examples 'issuable record that supports quick actions in its description
it "does not close the #{issuable_type}" do
write_note("/close")
- expect(page).to have_content '/close'
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_open
@@ -165,7 +164,6 @@ shared_examples 'issuable record that supports quick actions in its description
it "does not reopen the #{issuable_type}" do
write_note("/reopen")
- expect(page).to have_content '/reopen'
expect(page).not_to have_content 'Commands applied'
expect(issuable).to be_closed
@@ -195,10 +193,9 @@ shared_examples 'issuable record that supports quick actions in its description
visit public_send("namespace_project_#{issuable_type}_path", project.namespace, project, issuable)
end
- it "does not reopen the #{issuable_type}" do
+ it "does not change the #{issuable_type} title" do
write_note("/title Awesome new title")
- expect(page).to have_content '/title'
expect(page).not_to have_content 'Commands applied'
expect(issuable.reload.title).not_to eq 'Awesome new title'
diff --git a/spec/support/gitlab_verify.rb b/spec/support/gitlab_verify.rb
new file mode 100644
index 00000000000..13e2e37624d
--- /dev/null
+++ b/spec/support/gitlab_verify.rb
@@ -0,0 +1,45 @@
+RSpec.shared_examples 'Gitlab::Verify::BatchVerifier subclass' do
+ describe 'batching' do
+ let(:first_batch) { objects[0].id..objects[0].id }
+ let(:second_batch) { objects[1].id..objects[1].id }
+ let(:third_batch) { objects[2].id..objects[2].id }
+
+ it 'iterates through objects in batches' do
+ expect(collect_ranges).to eq([first_batch, second_batch, third_batch])
+ end
+
+ it 'allows the starting ID to be specified' do
+ expect(collect_ranges(start: second_batch.first)).to eq([second_batch, third_batch])
+ end
+
+ it 'allows the finishing ID to be specified' do
+ expect(collect_ranges(finish: second_batch.last)).to eq([first_batch, second_batch])
+ end
+ end
+end
+
+module GitlabVerifyHelpers
+ def collect_ranges(args = {})
+ verifier = described_class.new(args.merge(batch_size: 1))
+
+ collect_results(verifier).map { |range, _| range }
+ end
+
+ def collect_failures
+ verifier = described_class.new(batch_size: 1)
+
+ out = {}
+
+ collect_results(verifier).map { |_, failures| out.merge!(failures) }
+
+ out
+ end
+
+ def collect_results(verifier)
+ out = []
+
+ verifier.run_batches { |*args| out << args }
+
+ out
+ end
+end
diff --git a/spec/tasks/gitlab/lfs/check_rake_spec.rb b/spec/tasks/gitlab/lfs/check_rake_spec.rb
new file mode 100644
index 00000000000..2610edf8bac
--- /dev/null
+++ b/spec/tasks/gitlab/lfs/check_rake_spec.rb
@@ -0,0 +1,28 @@
+require 'rake_helper'
+
+describe 'gitlab:lfs rake tasks' do
+ describe 'check' do
+ let!(:lfs_object) { create(:lfs_object, :with_file, :correct_oid) }
+
+ before do
+ Rake.application.rake_require('tasks/gitlab/lfs/check')
+ stub_env('VERBOSE' => 'true')
+ end
+
+ it 'outputs the integrity check for each batch' do
+ expect { run_rake_task('gitlab:lfs:check') }.to output(/Failures: 0/).to_stdout
+ end
+
+ it 'errors out about missing files on the file system' do
+ FileUtils.rm_f(lfs_object.file.path)
+
+ expect { run_rake_task('gitlab:lfs:check') }.to output(/No such file.*#{Regexp.quote(lfs_object.file.path)}/).to_stdout
+ end
+
+ it 'errors out about invalid checksum' do
+ File.truncate(lfs_object.file.path, 0)
+
+ expect { run_rake_task('gitlab:lfs:check') }.to output(/Checksum mismatch/).to_stdout
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/traces_rake_spec.rb b/spec/tasks/gitlab/traces_rake_spec.rb
new file mode 100644
index 00000000000..bd18e8ffc1e
--- /dev/null
+++ b/spec/tasks/gitlab/traces_rake_spec.rb
@@ -0,0 +1,55 @@
+require 'rake_helper'
+
+describe 'gitlab:traces rake tasks' do
+ before do
+ Rake.application.rake_require 'tasks/gitlab/traces'
+ end
+
+ shared_examples 'passes the job id to worker' do
+ it do
+ expect(ArchiveTraceWorker).to receive(:bulk_perform_async).with([[job.id]])
+
+ run_rake_task('gitlab:traces:archive')
+ end
+ end
+
+ shared_examples 'does not pass the job id to worker' do
+ it do
+ expect(ArchiveTraceWorker).not_to receive(:bulk_perform_async)
+
+ run_rake_task('gitlab:traces:archive')
+ end
+ end
+
+ context 'when trace file stored in default path' do
+ let!(:job) { create(:ci_build, :success, :trace_live) }
+
+ it_behaves_like 'passes the job id to worker'
+ end
+
+ context 'when trace is stored in database' do
+ let!(:job) { create(:ci_build, :success) }
+
+ before do
+ job.update_column(:trace, 'trace in db')
+ end
+
+ it_behaves_like 'passes the job id to worker'
+ end
+
+ context 'when job has trace artifact' do
+ let!(:job) { create(:ci_build, :success) }
+
+ before do
+ create(:ci_job_artifact, :trace, job: job)
+ end
+
+ it_behaves_like 'does not pass the job id to worker'
+ end
+
+ context 'when job is not finished yet' do
+ let!(:build) { create(:ci_build, :running, :trace_live) }
+
+ it_behaves_like 'does not pass the job id to worker'
+ end
+end
diff --git a/spec/tasks/gitlab/uploads_rake_spec.rb b/spec/tasks/gitlab/uploads/check_rake_spec.rb
index ac0005e51e0..5d597c66133 100644
--- a/spec/tasks/gitlab/uploads_rake_spec.rb
+++ b/spec/tasks/gitlab/uploads/check_rake_spec.rb
@@ -5,23 +5,24 @@ describe 'gitlab:uploads rake tasks' do
let!(:upload) { create(:upload, path: Rails.root.join('spec/fixtures/banana_sample.gif')) }
before do
- Rake.application.rake_require 'tasks/gitlab/uploads'
+ Rake.application.rake_require('tasks/gitlab/uploads/check')
+ stub_env('VERBOSE' => 'true')
end
- it 'outputs the integrity check for each uploaded file' do
- expect { run_rake_task('gitlab:uploads:check') }.to output(/Checking file \(#{upload.id}\): #{Regexp.quote(upload.absolute_path)}/).to_stdout
+ it 'outputs the integrity check for each batch' do
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/Failures: 0/).to_stdout
end
it 'errors out about missing files on the file system' do
- create(:upload)
+ missing_upload = create(:upload)
- expect { run_rake_task('gitlab:uploads:check') }.to output(/File does not exist on the file system/).to_stdout
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/No such file.*#{Regexp.quote(missing_upload.absolute_path)}/).to_stdout
end
it 'errors out about invalid checksum' do
upload.update_column(:checksum, '01a3156db2cf4f67ec823680b40b7302f89ab39179124ad219f94919b8a1769e')
- expect { run_rake_task('gitlab:uploads:check') }.to output(/File checksum \(9e697aa09fe196909813ee36103e34f721fe47a5fdc8aac0e4e4ac47b9b38282\) does not match the one in the database \(#{upload.checksum}\)/).to_stdout
+ expect { run_rake_task('gitlab:uploads:check') }.to output(/Checksum mismatch/).to_stdout
end
end
end
diff --git a/spec/validators/url_placeholder_validator_spec.rb b/spec/validators/url_placeholder_validator_spec.rb
new file mode 100644
index 00000000000..b76d8acdf88
--- /dev/null
+++ b/spec/validators/url_placeholder_validator_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe UrlPlaceholderValidator do
+ let(:validator) { described_class.new(attributes: [:link_url], **options) }
+ let!(:badge) { build(:badge) }
+ let(:placeholder_url) { 'http://www.example.com/%{project_path}/%{project_id}/%{default_branch}/%{commit_sha}' }
+
+ subject { validator.validate_each(badge, :link_url, badge.link_url) }
+
+ describe '#validates_each' do
+ context 'with no options' do
+ let(:options) { {} }
+
+ it 'allows http and https protocols by default' do
+ expect(validator.send(:default_options)[:protocols]).to eq %w(http https)
+ end
+
+ it 'checks that the url structure is valid' do
+ badge.link_url = placeholder_url
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+
+ context 'with placeholder regex' do
+ let(:options) { { placeholder_regex: /(project_path|project_id|commit_sha|default_branch)/ } }
+
+ it 'checks that the url is valid and obviate placeholders that match regex' do
+ badge.link_url = placeholder_url
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+ end
+ end
+end
diff --git a/spec/validators/url_validator_spec.rb b/spec/validators/url_validator_spec.rb
new file mode 100644
index 00000000000..763dff181d2
--- /dev/null
+++ b/spec/validators/url_validator_spec.rb
@@ -0,0 +1,46 @@
+require 'spec_helper'
+
+describe UrlValidator do
+ let(:validator) { described_class.new(attributes: [:link_url], **options) }
+ let!(:badge) { build(:badge) }
+
+ subject { validator.validate_each(badge, :link_url, badge.link_url) }
+
+ describe '#validates_each' do
+ context 'with no options' do
+ let(:options) { {} }
+
+ it 'allows http and https protocols by default' do
+ expect(validator.send(:default_options)[:protocols]).to eq %w(http https)
+ end
+
+ it 'checks that the url structure is valid' do
+ badge.link_url = 'http://www.google.es/%{whatever}'
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+
+ context 'with protocols' do
+ let(:options) { { protocols: %w(http) } }
+
+ it 'allows urls with the defined protocols' do
+ badge.link_url = 'http://www.example.com'
+
+ subject
+
+ expect(badge.errors.empty?).to be true
+ end
+
+ it 'add error if the url protocol does not match the selected ones' do
+ badge.link_url = 'https://www.example.com'
+
+ subject
+
+ expect(badge.errors.empty?).to be false
+ end
+ end
+ end
+end
diff --git a/spec/views/projects/_home_panel.html.haml_spec.rb b/spec/views/projects/_home_panel.html.haml_spec.rb
index 62af946dcab..15fce65979b 100644
--- a/spec/views/projects/_home_panel.html.haml_spec.rb
+++ b/spec/views/projects/_home_panel.html.haml_spec.rb
@@ -1,7 +1,8 @@
require 'spec_helper'
describe 'projects/_home_panel' do
- let(:project) { create(:project, :public) }
+ let(:group) { create(:group) }
+ let(:project) { create(:project, :public, namespace: group) }
let(:notification_settings) do
user&.notification_settings_for(project)
@@ -35,4 +36,55 @@ describe 'projects/_home_panel' do
expect(rendered).not_to have_selector('.notification_dropdown')
end
end
+
+ context 'when project' do
+ let!(:user) { create(:user) }
+ let(:badges) { project.badges }
+
+ context 'has no badges' do
+ it 'should not render any badge' do
+ render
+
+ expect(rendered).to have_selector('.project-badges')
+ expect(rendered).not_to have_selector('.project-badges > a')
+ end
+ end
+
+ shared_examples 'show badges' do
+ it 'should render the all badges' do
+ render
+
+ expect(rendered).to have_selector('.project-badges a')
+
+ badges.each do |badge|
+ expect(rendered).to have_link(href: badge.rendered_link_url)
+ end
+ end
+ end
+
+ context 'only has group badges' do
+ before do
+ create(:group_badge, group: project.group)
+ end
+
+ it_behaves_like 'show badges'
+ end
+
+ context 'only has project badges' do
+ before do
+ create(:project_badge, project: project)
+ end
+
+ it_behaves_like 'show badges'
+ end
+
+ context 'has both group and project badges' do
+ before do
+ create(:project_badge, project: project)
+ create(:group_badge, group: project.group)
+ end
+
+ it_behaves_like 'show badges'
+ end
+ end
end
diff --git a/spec/workers/create_trace_artifact_worker_spec.rb b/spec/workers/archive_trace_worker_spec.rb
index 854abd9cca7..b768588c6e1 100644
--- a/spec/workers/create_trace_artifact_worker_spec.rb
+++ b/spec/workers/archive_trace_worker_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe CreateTraceArtifactWorker do
+describe ArchiveTraceWorker do
describe '#perform' do
subject { described_class.new.perform(job&.id) }
@@ -8,8 +8,7 @@ describe CreateTraceArtifactWorker do
let(:job) { create(:ci_build) }
it 'executes service' do
- expect_any_instance_of(Ci::CreateTraceArtifactService)
- .to receive(:execute).with(job)
+ expect_any_instance_of(Gitlab::Ci::Trace).to receive(:archive!)
subject
end
@@ -19,8 +18,7 @@ describe CreateTraceArtifactWorker do
let(:job) { nil }
it 'does not execute service' do
- expect_any_instance_of(Ci::CreateTraceArtifactService)
- .not_to receive(:execute)
+ expect_any_instance_of(Gitlab::Ci::Trace).not_to receive(:archive!)
subject
end
diff --git a/spec/workers/build_finished_worker_spec.rb b/spec/workers/build_finished_worker_spec.rb
index c7ff8cf3b92..acd8da11d8d 100644
--- a/spec/workers/build_finished_worker_spec.rb
+++ b/spec/workers/build_finished_worker_spec.rb
@@ -14,7 +14,7 @@ describe BuildFinishedWorker do
expect_any_instance_of(BuildTraceSectionsWorker).to receive(:perform)
expect_any_instance_of(BuildCoverageWorker).to receive(:perform)
expect(BuildHooksWorker).to receive(:perform_async)
- expect(CreateTraceArtifactWorker).to receive(:perform_async)
+ expect(ArchiveTraceWorker).to receive(:perform_async)
described_class.new.perform(build.id)
end
diff --git a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
index 68cfe9d5545..615462380e0 100644
--- a/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
+++ b/spec/workers/concerns/gitlab/github_import/object_importer_spec.rb
@@ -25,7 +25,7 @@ describe Gitlab::GithubImport::ObjectImporter do
importer_class = double(:importer_class)
importer_instance = double(:importer_instance)
representation = double(:representation)
- project = double(:project, path_with_namespace: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar')
client = double(:client)
expect(worker)
diff --git a/spec/workers/concerns/pipeline_background_queue_spec.rb b/spec/workers/concerns/pipeline_background_queue_spec.rb
new file mode 100644
index 00000000000..24c0a3c6a20
--- /dev/null
+++ b/spec/workers/concerns/pipeline_background_queue_spec.rb
@@ -0,0 +1,19 @@
+require 'spec_helper'
+
+describe PipelineBackgroundQueue do
+ let(:worker) do
+ Class.new do
+ def self.name
+ 'DummyWorker'
+ end
+
+ include ApplicationWorker
+ include PipelineBackgroundQueue
+ end
+ end
+
+ it 'sets a default object storage queue automatically' do
+ expect(worker.sidekiq_options['queue'])
+ .to eq 'pipeline_background:dummy'
+ end
+end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index 47297de738b..74539a7e493 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -195,6 +195,12 @@ describe GitGarbageCollectWorker do
expect(File.exist?(bitmap_path(after_packs.first))).to eq(bitmaps_enabled)
end
+
+ it 'cleans up repository after finishing' do
+ expect_any_instance_of(Project).to receive(:cleanup).and_call_original
+
+ subject.perform(project.id, 'gc', lease_key, lease_uuid)
+ end
end
context 'with bitmaps enabled' do
diff --git a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
index 7c8c665a9b3..48e7eaf32fc 100644
--- a/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_diff_note_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportDiffNoteWorker do
describe '#import' do
it 'imports a diff note' do
- project = double(:project, path_with_namespace: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar')
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
index 4116380ff4d..8cf6ac15919 100644
--- a/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_issue_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportIssueWorker do
describe '#import' do
it 'imports an issue' do
- project = double(:project, path_with_namespace: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar')
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_note_worker_spec.rb b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
index 0ca825a722b..677697c02df 100644
--- a/spec/workers/gitlab/github_import/import_note_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_note_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportNoteWorker do
describe '#import' do
it 'imports a note' do
- project = double(:project, path_with_namespace: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar')
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
index d49f560af42..e287ddbe0d7 100644
--- a/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/import_pull_request_worker_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::GithubImport::ImportPullRequestWorker do
describe '#import' do
it 'imports a pull request' do
- project = double(:project, path_with_namespace: 'foo/bar')
+ project = double(:project, full_path: 'foo/bar')
client = double(:client)
importer = double(:importer)
hash = {
diff --git a/spec/workers/process_commit_worker_spec.rb b/spec/workers/process_commit_worker_spec.rb
index 76ef57b6b1e..ac79d9c0ac1 100644
--- a/spec/workers/process_commit_worker_spec.rb
+++ b/spec/workers/process_commit_worker_spec.rb
@@ -20,32 +20,6 @@ describe ProcessCommitWorker do
worker.perform(project.id, -1, commit.to_hash)
end
- context 'when commit is a merge request merge commit' do
- let(:merge_request) do
- create(:merge_request,
- description: "Closes #{issue.to_reference}",
- source_branch: 'feature-merged',
- target_branch: 'master',
- source_project: project)
- end
-
- let(:commit) do
- project.repository.create_branch('feature-merged', 'feature')
-
- sha = project.repository.merge(user,
- merge_request.diff_head_sha,
- merge_request,
- "Closes #{issue.to_reference}")
- project.repository.commit(sha)
- end
-
- it 'it does not close any issues from the commit message' do
- expect(worker).not_to receive(:close_issues)
-
- worker.perform(project.id, user.id, commit.to_hash)
- end
- end
-
it 'processes the commit message' do
expect(worker).to receive(:process_commit_message).and_call_original
@@ -73,13 +47,21 @@ describe ProcessCommitWorker do
describe '#process_commit_message' do
context 'when pushing to the default branch' do
- it 'closes issues that should be closed per the commit message' do
+ before do
allow(commit).to receive(:safe_message).and_return("Closes #{issue.to_reference}")
+ end
+ it 'closes issues that should be closed per the commit message' do
expect(worker).to receive(:close_issues).with(project, user, user, commit, [issue])
worker.process_commit_message(project, commit, user, user, true)
end
+
+ it 'creates cross references' do
+ expect(commit).to receive(:create_cross_references!).with(user, [issue])
+
+ worker.process_commit_message(project, commit, user, user, true)
+ end
end
context 'when pushing to a non-default branch' do
@@ -90,12 +72,44 @@ describe ProcessCommitWorker do
worker.process_commit_message(project, commit, user, user, false)
end
+
+ it 'does not create cross references' do
+ expect(commit).to receive(:create_cross_references!).with(user, [])
+
+ worker.process_commit_message(project, commit, user, user, false)
+ end
end
- it 'creates cross references' do
- expect(commit).to receive(:create_cross_references!)
+ context 'when commit is a merge request merge commit to the default branch' do
+ let(:merge_request) do
+ create(:merge_request,
+ description: "Closes #{issue.to_reference}",
+ source_branch: 'feature-merged',
+ target_branch: 'master',
+ source_project: project)
+ end
- worker.process_commit_message(project, commit, user, user)
+ let(:commit) do
+ project.repository.create_branch('feature-merged', 'feature')
+
+ MergeRequests::MergeService
+ .new(project, merge_request.author)
+ .execute(merge_request)
+
+ merge_request.reload.merge_commit
+ end
+
+ it 'does not close any issues from the commit message' do
+ expect(worker).not_to receive(:close_issues)
+
+ worker.process_commit_message(project, commit, user, user, true)
+ end
+
+ it 'still creates cross references' do
+ expect(commit).to receive(:create_cross_references!).with(user, [])
+
+ worker.process_commit_message(project, commit, user, user, true)
+ end
end
end