Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/alert_management/components/alert_management_list.vue4
-rw-r--r--app/assets/javascripts/behaviors/markdown/render_mermaid.js2
-rw-r--r--app/assets/javascripts/ci_variable_list/constants.js2
-rw-r--r--app/assets/javascripts/ide/services/index.js4
-rw-r--r--app/assets/javascripts/ide/stores/actions/tree.js2
-rw-r--r--app/assets/javascripts/image_diff/helpers/badge_helper.js4
-rw-r--r--app/assets/javascripts/image_diff/helpers/dom_helper.js4
-rw-r--r--app/assets/javascripts/image_diff/image_diff.js2
-rw-r--r--app/assets/javascripts/notes.js2
-rw-r--r--app/controllers/projects/design_management/designs/raw_images_controller.rb30
-rw-r--r--app/controllers/projects/design_management/designs/resized_image_controller.rb46
-rw-r--r--app/controllers/projects/design_management/designs_controller.rb21
-rw-r--r--app/controllers/projects/issues_controller.rb1
-rw-r--r--app/graphql/types/alert_management/alert_sort_enum.rb6
-rw-r--r--changelogs/unreleased/216851-graphql-externallypaginatedarrayconnection-can-return-incorrect-nu.yml5
-rw-r--r--changelogs/unreleased/36810-webide-branch-with-path.yml5
-rw-r--r--changelogs/unreleased/change-var-to-variable.yml5
-rw-r--r--changelogs/unreleased/sh-handle-invalid-gitattributes.yml5
-rw-r--r--config/routes/issues.rb1
-rw-r--r--config/routes/project.rb7
-rw-r--r--doc/api/graphql/reference/gitlab_schema.graphql134
-rw-r--r--doc/api/graphql/reference/gitlab_schema.json343
-rw-r--r--doc/api/graphql/reference/index.md10
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/user/analytics/value_stream_analytics.md22
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/gitlab/git/attributes_parser.rb2
-rw-r--r--lib/gitlab/graphql/pagination/externally_paginated_array_connection.rb14
-rw-r--r--locale/gitlab.pot2
-rw-r--r--spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb153
-rw-r--r--spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb158
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb27
-rw-r--r--spec/frontend/alert_management/components/alert_management_list_spec.js42
-rw-r--r--spec/frontend/ci_variable_list/services/mock_data.js12
-rw-r--r--spec/frontend/ci_variable_list/store/mutations_spec.js2
-rw-r--r--spec/frontend/ide/services/index_spec.js33
-rw-r--r--spec/frontend/image_diff/helpers/badge_helper_spec.js (renamed from spec/javascripts/image_diff/helpers/badge_helper_spec.js)4
-rw-r--r--spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js (renamed from spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js)4
-rw-r--r--spec/frontend/image_diff/helpers/dom_helper_spec.js (renamed from spec/javascripts/image_diff/helpers/dom_helper_spec.js)4
-rw-r--r--spec/frontend/image_diff/helpers/utils_helper_spec.js (renamed from spec/javascripts/image_diff/helpers/utils_helper_spec.js)0
-rw-r--r--spec/frontend/image_diff/image_badge_spec.js (renamed from spec/javascripts/image_diff/image_badge_spec.js)2
-rw-r--r--spec/frontend/image_diff/image_diff_spec.js (renamed from spec/javascripts/image_diff/image_diff_spec.js)54
-rw-r--r--spec/frontend/image_diff/mock_data.js (renamed from spec/javascripts/image_diff/mock_data.js)0
-rw-r--r--spec/frontend/image_diff/replaced_image_diff_spec.js (renamed from spec/javascripts/image_diff/replaced_image_diff_spec.js)53
-rw-r--r--spec/frontend/vue_shared/components/ci_badge_link_spec.js (renamed from spec/javascripts/vue_shared/components/ci_badge_link_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/ci_icon_spec.js (renamed from spec/javascripts/vue_shared/components/ci_icon_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/content_viewer/content_viewer_spec.js (renamed from spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js)8
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js (renamed from spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js)6
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js (renamed from spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js)4
-rw-r--r--spec/frontend/vue_shared/components/dropdown/dropdown_hidden_input_spec.js (renamed from spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/dropdown/mock_data.js (renamed from spec/javascripts/vue_shared/components/dropdown/mock_data.js)0
-rw-r--r--spec/frontend/vue_shared/components/file_finder/item_spec.js (renamed from spec/javascripts/vue_shared/components/file_finder/item_spec.js)6
-rw-r--r--spec/frontend/vue_shared/components/filtered_search_dropdown_spec.js (renamed from spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js)4
-rw-r--r--spec/frontend/vue_shared/components/gl_countdown_spec.js (renamed from spec/javascripts/vue_shared/components/gl_countdown_spec.js)26
-rw-r--r--spec/frontend/vue_shared/components/header_ci_component_spec.js (renamed from spec/javascripts/vue_shared/components/header_ci_component_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/markdown/toolbar_spec.js (renamed from spec/javascripts/vue_shared/components/markdown/toolbar_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/navigation_tabs_spec.js (renamed from spec/javascripts/vue_shared/components/navigation_tabs_spec.js)4
-rw-r--r--spec/frontend/vue_shared/components/pikaday_spec.js (renamed from spec/javascripts/vue_shared/components/pikaday_spec.js)4
-rw-r--r--spec/frontend/vue_shared/components/project_avatar/default_spec.js (renamed from spec/javascripts/vue_shared/components/project_avatar/default_spec.js)8
-rw-r--r--spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js (renamed from spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js)8
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js2
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js5
-rw-r--r--spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js (renamed from spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js)0
-rw-r--r--spec/frontend/vue_shared/components/stacked_progress_bar_spec.js (renamed from spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/tabs/tab_spec.js (renamed from spec/javascripts/vue_shared/components/tabs/tab_spec.js)2
-rw-r--r--spec/frontend/vue_shared/components/tabs/tabs_spec.js (renamed from spec/javascripts/vue_shared/components/tabs/tabs_spec.js)31
-rw-r--r--spec/frontend/vue_shared/components/toggle_button_spec.js (renamed from spec/javascripts/vue_shared/components/toggle_button_spec.js)6
-rw-r--r--spec/frontend/vue_shared/components/user_avatar/user_avatar_svg_spec.js27
-rw-r--r--spec/javascripts/ide/stores/actions/tree_spec.js3
-rw-r--r--spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js29
-rw-r--r--spec/lib/gitlab/git/attributes_parser_spec.rb8
-rw-r--r--spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb14
-rw-r--r--spec/models/design_management/design_spec.rb7
-rw-r--r--spec/routing/project_routing_spec.rb16
78 files changed, 1269 insertions, 228 deletions
diff --git a/app/assets/javascripts/alert_management/components/alert_management_list.vue b/app/assets/javascripts/alert_management/components/alert_management_list.vue
index f1716182e5f..7fe74eb1da8 100644
--- a/app/assets/javascripts/alert_management/components/alert_management_list.vue
+++ b/app/assets/javascripts/alert_management/components/alert_management_list.vue
@@ -197,11 +197,11 @@ export default {
</template>
<template #cell(startedAt)="{ item }">
- <time-ago :time="item.startedAt" />
+ <time-ago v-if="item.startedAt" :time="item.startedAt" />
</template>
<template #cell(endedAt)="{ item }">
- <time-ago :time="item.endedAt" />
+ <time-ago v-if="item.endedAt" :time="item.endedAt" />
</template>
<template #cell(title)="{ item }">
diff --git a/app/assets/javascripts/behaviors/markdown/render_mermaid.js b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
index 057cdb6cc4c..e42f6e5ba48 100644
--- a/app/assets/javascripts/behaviors/markdown/render_mermaid.js
+++ b/app/assets/javascripts/behaviors/markdown/render_mermaid.js
@@ -82,7 +82,7 @@ function renderMermaidEl(el) {
return;
}
- svg.classList.add('mermaid');
+ svg.classList.add('mermaid', 'mw-100');
// pre > code > svg
svg.closest('pre').replaceWith(svg);
diff --git a/app/assets/javascripts/ci_variable_list/constants.js b/app/assets/javascripts/ci_variable_list/constants.js
index 5fe1e32e37e..a4db6481720 100644
--- a/app/assets/javascripts/ci_variable_list/constants.js
+++ b/app/assets/javascripts/ci_variable_list/constants.js
@@ -4,7 +4,7 @@ import { __ } from '~/locale';
export const ADD_CI_VARIABLE_MODAL_ID = 'add-ci-variable';
export const displayText = {
- variableText: __('Var'),
+ variableText: __('Variable'),
fileText: __('File'),
allEnvironmentsText: __('All (default)'),
};
diff --git a/app/assets/javascripts/ide/services/index.js b/app/assets/javascripts/ide/services/index.js
index 3adf0cf073f..3d11c683711 100644
--- a/app/assets/javascripts/ide/services/index.js
+++ b/app/assets/javascripts/ide/services/index.js
@@ -88,8 +88,8 @@ export default {
commit(projectId, payload) {
return Api.commitMultiple(projectId, payload);
},
- getFiles(projectUrl, ref) {
- const url = `${projectUrl}/-/files/${ref}`;
+ getFiles(projectPath, ref) {
+ const url = `${gon.relative_url_root}/${projectPath}/-/files/${ref}`;
return axios.get(url, { params: { format: 'json' } });
},
lastCommitPipelines({ getters }) {
diff --git a/app/assets/javascripts/ide/stores/actions/tree.js b/app/assets/javascripts/ide/stores/actions/tree.js
index 7d48f0adc4c..1ca608f1287 100644
--- a/app/assets/javascripts/ide/stores/actions/tree.js
+++ b/app/assets/javascripts/ide/stores/actions/tree.js
@@ -59,7 +59,7 @@ export const getFiles = ({ state, commit, dispatch }, payload = {}) =>
commit(types.CREATE_TREE, { treePath: `${projectId}/${branchId}` });
service
- .getFiles(selectedProject.web_url, ref)
+ .getFiles(selectedProject.path_with_namespace, ref)
.then(({ data }) => {
const { entries, treeList } = decorateFiles({
data,
diff --git a/app/assets/javascripts/image_diff/helpers/badge_helper.js b/app/assets/javascripts/image_diff/helpers/badge_helper.js
index 7921650e8a0..229e0a62c51 100644
--- a/app/assets/javascripts/image_diff/helpers/badge_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/badge_helper.js
@@ -15,7 +15,7 @@ export function createImageBadge(noteId, { x, y }, classNames = []) {
export function addImageBadge(containerEl, { coordinate, badgeText, noteId }) {
const buttonEl = createImageBadge(noteId, coordinate, ['badge', 'badge-pill']);
- buttonEl.innerText = badgeText;
+ buttonEl.textContent = badgeText;
containerEl.appendChild(buttonEl);
}
@@ -32,6 +32,6 @@ export function addAvatarBadge(el, event) {
// Add badge to new comment
const avatarBadgeEl = el.querySelector(`#${noteId} .badge`);
- avatarBadgeEl.innerText = badgeNumber;
+ avatarBadgeEl.textContent = badgeNumber;
avatarBadgeEl.classList.remove('hidden');
}
diff --git a/app/assets/javascripts/image_diff/helpers/dom_helper.js b/app/assets/javascripts/image_diff/helpers/dom_helper.js
index 74ca907c99f..a61e5f01f9b 100644
--- a/app/assets/javascripts/image_diff/helpers/dom_helper.js
+++ b/app/assets/javascripts/image_diff/helpers/dom_helper.js
@@ -11,12 +11,12 @@ export function setPositionDataAttribute(el, options) {
export function updateDiscussionAvatarBadgeNumber(discussionEl, newBadgeNumber) {
const avatarBadgeEl = discussionEl.querySelector('.image-diff-avatar-link .badge');
- avatarBadgeEl.innerText = newBadgeNumber;
+ avatarBadgeEl.textContent = newBadgeNumber;
}
export function updateDiscussionBadgeNumber(discussionEl, newBadgeNumber) {
const discussionBadgeEl = discussionEl.querySelector('.badge');
- discussionBadgeEl.innerText = newBadgeNumber;
+ discussionBadgeEl.textContent = newBadgeNumber;
}
export function toggleCollapsed(event) {
diff --git a/app/assets/javascripts/image_diff/image_diff.js b/app/assets/javascripts/image_diff/image_diff.js
index 89f696dd1d8..079f4a63f6e 100644
--- a/app/assets/javascripts/image_diff/image_diff.js
+++ b/app/assets/javascripts/image_diff/image_diff.js
@@ -128,7 +128,7 @@ export default class ImageDiff {
const updatedBadgeNumber = index;
const discussionEl = this.el.querySelector(`#discussion_${discussionId}`);
- imageBadgeEls[index].innerText = updatedBadgeNumber;
+ imageBadgeEls[index].textContent = updatedBadgeNumber;
imageDiffHelper.updateDiscussionBadgeNumber(discussionEl, updatedBadgeNumber);
imageDiffHelper.updateDiscussionAvatarBadgeNumber(discussionEl, updatedBadgeNumber);
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index a525f660801..6e695de447d 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -1396,7 +1396,7 @@ export default class Notes {
}
/**
- * Check if note does not exists on page
+ * Check if note does not exist on page
*/
static isNewNote(noteEntity, noteIds) {
return $.inArray(noteEntity.id, noteIds) === -1;
diff --git a/app/controllers/projects/design_management/designs/raw_images_controller.rb b/app/controllers/projects/design_management/designs/raw_images_controller.rb
new file mode 100644
index 00000000000..beb7e9d294b
--- /dev/null
+++ b/app/controllers/projects/design_management/designs/raw_images_controller.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+# Returns full-size design images
+module Projects
+ module DesignManagement
+ module Designs
+ class RawImagesController < Projects::DesignManagement::DesignsController
+ include SendsBlob
+
+ skip_before_action :default_cache_headers, only: :show
+
+ def show
+ blob = design_repository.blob_at(ref, design.full_path)
+
+ send_blob(design_repository, blob, inline: false, allow_caching: project.public?)
+ end
+
+ private
+
+ def design_repository
+ @design_repository ||= project.design_repository
+ end
+
+ def ref
+ sha || design_repository.root_ref
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/design_management/designs/resized_image_controller.rb b/app/controllers/projects/design_management/designs/resized_image_controller.rb
new file mode 100644
index 00000000000..50a997f32db
--- /dev/null
+++ b/app/controllers/projects/design_management/designs/resized_image_controller.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+# Returns smaller sized design images
+module Projects
+ module DesignManagement
+ module Designs
+ class ResizedImageController < Projects::DesignManagement::DesignsController
+ include SendFileUpload
+
+ before_action :validate_size!
+
+ skip_before_action :default_cache_headers, only: :show
+
+ def show
+ relation = design.actions
+ relation = relation.up_to_version(sha) if sha
+ action = relation.most_recent.first
+
+ return render_404 unless action
+
+ # This controller returns a 404 if the the `size` param
+ # is not one of our specific sizes, so using `send` here is safe.
+ uploader = action.public_send(:"image_#{size}") # rubocop:disable GitlabSecurity/PublicSend
+
+ return render_404 unless uploader.file # The image has not been processed
+
+ if stale?(etag: action.cache_key)
+ workhorse_set_content_type!
+
+ send_upload(uploader, attachment: design.filename)
+ end
+ end
+
+ private
+
+ def validate_size!
+ render_404 unless ::DesignManagement::DESIGN_IMAGE_SIZES.include?(size)
+ end
+
+ def size
+ params[:id]
+ end
+ end
+ end
+ end
+end
diff --git a/app/controllers/projects/design_management/designs_controller.rb b/app/controllers/projects/design_management/designs_controller.rb
new file mode 100644
index 00000000000..fec09fa9515
--- /dev/null
+++ b/app/controllers/projects/design_management/designs_controller.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+class Projects::DesignManagement::DesignsController < Projects::ApplicationController
+ before_action :authorize_read_design!
+
+ private
+
+ def authorize_read_design!
+ unless can?(current_user, :read_design, design)
+ access_denied!
+ end
+ end
+
+ def design
+ @design ||= project.designs.find(params[:design_id])
+ end
+
+ def sha
+ params[:sha].presence
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index d759983dafa..fa4b91c5e02 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -21,7 +21,6 @@ class Projects::IssuesController < Projects::ApplicationController
prepend_before_action(only: [:index]) { authenticate_sessionless_user!(:rss) }
prepend_before_action(only: [:calendar]) { authenticate_sessionless_user!(:ics) }
prepend_before_action :authenticate_user!, only: [:new, :export_csv]
- # designs is only applicable to EE, but defining a prepend_before_action in EE code would overwrite this
prepend_before_action :store_uri, only: [:new, :show, :designs]
before_action :whitelist_query_limiting, only: [:create, :create_merge_request, :move, :bulk_update]
diff --git a/app/graphql/types/alert_management/alert_sort_enum.rb b/app/graphql/types/alert_management/alert_sort_enum.rb
index 76ef43d9dcf..e6d38af8170 100644
--- a/app/graphql/types/alert_management/alert_sort_enum.rb
+++ b/app/graphql/types/alert_management/alert_sort_enum.rb
@@ -11,9 +11,9 @@ module Types
value 'END_TIME_ASC', 'End time by ascending order', value: :end_time_asc
value 'END_TIME_DESC', 'End time by descending order', value: :end_time_desc
value 'CREATED_TIME_ASC', 'Created time by ascending order', value: :created_at_asc
- value 'CREATED_TIME_DESC', 'Created time by ascending order', value: :created_at_desc
- value 'UPDATED_TIME_ASC', 'Created time by ascending order', value: :updated_at_desc
- value 'UPDATED_TIME_DESC', 'Created time by ascending order', value: :updated_at_desc
+ value 'CREATED_TIME_DESC', 'Created time by descending order', value: :created_at_desc
+ value 'UPDATED_TIME_ASC', 'Created time by ascending order', value: :updated_at_asc
+ value 'UPDATED_TIME_DESC', 'Created time by descending order', value: :updated_at_desc
value 'EVENTS_COUNT_ASC', 'Events count by ascending order', value: :events_count_asc
value 'EVENTS_COUNT_DESC', 'Events count by descending order', value: :events_count_desc
value 'SEVERITY_ASC', 'Severity by ascending order', value: :severity_asc
diff --git a/changelogs/unreleased/216851-graphql-externallypaginatedarrayconnection-can-return-incorrect-nu.yml b/changelogs/unreleased/216851-graphql-externallypaginatedarrayconnection-can-return-incorrect-nu.yml
new file mode 100644
index 00000000000..dd36d52f1c4
--- /dev/null
+++ b/changelogs/unreleased/216851-graphql-externallypaginatedarrayconnection-can-return-incorrect-nu.yml
@@ -0,0 +1,5 @@
+---
+title: Fix incorrect number of errors returned when querying sentry errors
+merge_request: 31252
+author:
+type: fixed
diff --git a/changelogs/unreleased/36810-webide-branch-with-path.yml b/changelogs/unreleased/36810-webide-branch-with-path.yml
new file mode 100644
index 00000000000..2238101799c
--- /dev/null
+++ b/changelogs/unreleased/36810-webide-branch-with-path.yml
@@ -0,0 +1,5 @@
+---
+title: In WebIDE get files with relative path instead of web_url
+merge_request: 31478
+author:
+type: fixed
diff --git a/changelogs/unreleased/change-var-to-variable.yml b/changelogs/unreleased/change-var-to-variable.yml
new file mode 100644
index 00000000000..ec2983696d4
--- /dev/null
+++ b/changelogs/unreleased/change-var-to-variable.yml
@@ -0,0 +1,5 @@
+---
+title: Change Var to Variable text
+merge_request: 30878
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-handle-invalid-gitattributes.yml b/changelogs/unreleased/sh-handle-invalid-gitattributes.yml
new file mode 100644
index 00000000000..56378d18297
--- /dev/null
+++ b/changelogs/unreleased/sh-handle-invalid-gitattributes.yml
@@ -0,0 +1,5 @@
+---
+title: Ignore .gitattributes if they contain invalid byte sequences
+merge_request: 30922
+author:
+type: fixed
diff --git a/config/routes/issues.rb b/config/routes/issues.rb
index 51b4637b89f..04a935c1016 100644
--- a/config/routes/issues.rb
+++ b/config/routes/issues.rb
@@ -13,6 +13,7 @@ resources :issues, concerns: :awardable, constraints: { id: /\d+/ } do
get :realtime_changes
post :create_merge_request
get :discussions, format: :json
+ get '/designs(/*vueroute)', to: 'issues#designs', as: :designs, format: false
end
collection do
diff --git a/config/routes/project.rb b/config/routes/project.rb
index ac8f621b2b6..0cd880a8c46 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -305,6 +305,13 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
+ namespace :design_management do
+ namespace :designs, path: 'designs/:design_id(/:sha)', constraints: -> (params) { params[:sha].nil? || Gitlab::Git.commit_id?(params[:sha]) } do
+ resource :raw_image, only: :show
+ resources :resized_image, only: :show, constraints: -> (params) { DesignManagement::DESIGN_IMAGE_SIZES.include?(params[:id]) }
+ end
+ end
+
draw :issues
draw :merge_requests
diff --git a/doc/api/graphql/reference/gitlab_schema.graphql b/doc/api/graphql/reference/gitlab_schema.graphql
index 89b3bbac938..cc21b169120 100644
--- a/doc/api/graphql/reference/gitlab_schema.graphql
+++ b/doc/api/graphql/reference/gitlab_schema.graphql
@@ -258,7 +258,7 @@ enum AlertManagementAlertSort {
CREATED_TIME_ASC
"""
- Created time by ascending order
+ Created time by descending order
"""
CREATED_TIME_DESC
@@ -318,7 +318,7 @@ enum AlertManagementAlertSort {
UPDATED_TIME_ASC
"""
- Created time by ascending order
+ Created time by descending order
"""
UPDATED_TIME_DESC
@@ -4303,6 +4303,41 @@ type Group {
): VulnerabilityConnection
"""
+ Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups
+ """
+ vulnerabilitiesCountByDayAndSeverity(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Last day for which to fetch vulnerability history
+ """
+ endDate: ISO8601Date!
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+
+ """
+ First day for which to fetch vulnerability history
+ """
+ startDate: ISO8601Date!
+ ): VulnerabilitiesCountByDayAndSeverityConnection
+
+ """
Web URL of the group
"""
webUrl: String!
@@ -4324,6 +4359,11 @@ enum HealthStatus {
onTrack
}
+"""
+An ISO 8601-encoded date
+"""
+scalar ISO8601Date
+
type InstanceSecurityDashboard {
"""
Projects selected in Instance Security Dashboard
@@ -8223,6 +8263,41 @@ type Query {
"""
state: [VulnerabilityState!]
): VulnerabilityConnection
+
+ """
+ Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard
+ """
+ vulnerabilitiesCountByDayAndSeverity(
+ """
+ Returns the elements in the list that come after the specified cursor.
+ """
+ after: String
+
+ """
+ Returns the elements in the list that come before the specified cursor.
+ """
+ before: String
+
+ """
+ Last day for which to fetch vulnerability history
+ """
+ endDate: ISO8601Date!
+
+ """
+ Returns the first _n_ elements from the list.
+ """
+ first: Int
+
+ """
+ Returns the last _n_ elements from the list.
+ """
+ last: Int
+
+ """
+ First day for which to fetch vulnerability history
+ """
+ startDate: ISO8601Date!
+ ): VulnerabilitiesCountByDayAndSeverityConnection
}
"""
@@ -10721,6 +10796,61 @@ enum VisibilityScopesEnum {
}
"""
+Represents the number of vulnerabilities for a particular severity on a particular day
+"""
+type VulnerabilitiesCountByDayAndSeverity {
+ """
+ Number of vulnerabilities
+ """
+ count: Int
+
+ """
+ Date for the count
+ """
+ day: ISO8601Date
+
+ """
+ Severity of the counted vulnerabilities
+ """
+ severity: VulnerabilitySeverity
+}
+
+"""
+The connection type for VulnerabilitiesCountByDayAndSeverity.
+"""
+type VulnerabilitiesCountByDayAndSeverityConnection {
+ """
+ A list of edges.
+ """
+ edges: [VulnerabilitiesCountByDayAndSeverityEdge]
+
+ """
+ A list of nodes.
+ """
+ nodes: [VulnerabilitiesCountByDayAndSeverity]
+
+ """
+ Information to aid in pagination.
+ """
+ pageInfo: PageInfo!
+}
+
+"""
+An edge in a connection.
+"""
+type VulnerabilitiesCountByDayAndSeverityEdge {
+ """
+ A cursor for use in pagination.
+ """
+ cursor: String!
+
+ """
+ The item at the end of the edge.
+ """
+ node: VulnerabilitiesCountByDayAndSeverity
+}
+
+"""
Represents a vulnerability.
"""
type Vulnerability {
diff --git a/doc/api/graphql/reference/gitlab_schema.json b/doc/api/graphql/reference/gitlab_schema.json
index 3e845667e80..c7614b8dd6b 100644
--- a/doc/api/graphql/reference/gitlab_schema.json
+++ b/doc/api/graphql/reference/gitlab_schema.json
@@ -786,7 +786,7 @@
},
{
"name": "CREATED_TIME_DESC",
- "description": "Created time by ascending order",
+ "description": "Created time by descending order",
"isDeprecated": false,
"deprecationReason": null
},
@@ -798,7 +798,7 @@
},
{
"name": "UPDATED_TIME_DESC",
- "description": "Created time by ascending order",
+ "description": "Created time by descending order",
"isDeprecated": false,
"deprecationReason": null
},
@@ -11940,6 +11940,87 @@
"deprecationReason": null
},
{
+ "name": "vulnerabilitiesCountByDayAndSeverity",
+ "description": "Number of vulnerabilities per severity level, per day, for the projects in the group and its subgroups",
+ "args": [
+ {
+ "name": "startDate",
+ "description": "First day for which to fetch vulnerability history",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "endDate",
+ "description": "Last day for which to fetch vulnerability history",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
"name": "webUrl",
"description": "Web URL of the group",
"args": [
@@ -12036,6 +12117,16 @@
"possibleTypes": null
},
{
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "description": "An ISO 8601-encoded date",
+ "fields": null,
+ "inputFields": null,
+ "interfaces": null,
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
"kind": "OBJECT",
"name": "InstanceSecurityDashboard",
"description": null,
@@ -24232,6 +24323,87 @@
},
"isDeprecated": false,
"deprecationReason": null
+ },
+ {
+ "name": "vulnerabilitiesCountByDayAndSeverity",
+ "description": "Number of vulnerabilities per severity level, per day, for the projects on the current user's instance security dashboard",
+ "args": [
+ {
+ "name": "startDate",
+ "description": "First day for which to fetch vulnerability history",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "endDate",
+ "description": "Last day for which to fetch vulnerability history",
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "ofType": null
+ }
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "after",
+ "description": "Returns the elements in the list that come after the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "before",
+ "description": "Returns the elements in the list that come before the specified cursor.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "first",
+ "description": "Returns the first _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ },
+ {
+ "name": "last",
+ "description": "Returns the last _n_ elements from the list.",
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "defaultValue": null
+ }
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
}
],
"inputFields": null,
@@ -31895,6 +32067,173 @@
},
{
"kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverity",
+ "description": "Represents the number of vulnerabilities for a particular severity on a particular day",
+ "fields": [
+ {
+ "name": "count",
+ "description": "Number of vulnerabilities",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "Int",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "day",
+ "description": "Date for the count",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "SCALAR",
+ "name": "ISO8601Date",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "severity",
+ "description": "Severity of the counted vulnerabilities",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "ENUM",
+ "name": "VulnerabilitySeverity",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverityConnection",
+ "description": "The connection type for VulnerabilitiesCountByDayAndSeverity.",
+ "fields": [
+ {
+ "name": "edges",
+ "description": "A list of edges.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverityEdge",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "nodes",
+ "description": "A list of nodes.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "LIST",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverity",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "pageInfo",
+ "description": "Information to aid in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "OBJECT",
+ "name": "PageInfo",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverityEdge",
+ "description": "An edge in a connection.",
+ "fields": [
+ {
+ "name": "cursor",
+ "description": "A cursor for use in pagination.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "NON_NULL",
+ "name": null,
+ "ofType": {
+ "kind": "SCALAR",
+ "name": "String",
+ "ofType": null
+ }
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ },
+ {
+ "name": "node",
+ "description": "The item at the end of the edge.",
+ "args": [
+
+ ],
+ "type": {
+ "kind": "OBJECT",
+ "name": "VulnerabilitiesCountByDayAndSeverity",
+ "ofType": null
+ },
+ "isDeprecated": false,
+ "deprecationReason": null
+ }
+ ],
+ "inputFields": null,
+ "interfaces": [
+
+ ],
+ "enumValues": null,
+ "possibleTypes": null
+ },
+ {
+ "kind": "OBJECT",
"name": "Vulnerability",
"description": "Represents a vulnerability.",
"fields": [
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 3ca7164bff5..f289a057cbc 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -1633,6 +1633,16 @@ Autogenerated return type of UpdateSnippet
| --- | ---- | ---------- |
| `createSnippet` | Boolean! | Indicates the user can perform `create_snippet` on this resource |
+## VulnerabilitiesCountByDayAndSeverity
+
+Represents the number of vulnerabilities for a particular severity on a particular day
+
+| Name | Type | Description |
+| --- | ---- | ---------- |
+| `count` | Int | Number of vulnerabilities |
+| `day` | ISO8601Date | Date for the count |
+| `severity` | VulnerabilitySeverity | Severity of the counted vulnerabilities |
+
## Vulnerability
Represents a vulnerability.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 507e548b8d8..a7524070494 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -756,7 +756,7 @@ Note that `script: rake test` has been overwritten by `script: rake rspec`.
If you do want to include the `rake test`, see [`before_script` and `after_script`](#before_script-and-after_script).
-`.tests` in this example is a [hidden key](#hide-jobs), but it's
+`.tests` in this example is a [hidden job](#hide-jobs), but it's
possible to inherit from regular jobs as well.
`extends` supports multi-level inheritance, however it's not recommended to
diff --git a/doc/user/analytics/value_stream_analytics.md b/doc/user/analytics/value_stream_analytics.md
index 1ded4a0cf0a..a544de60413 100644
--- a/doc/user/analytics/value_stream_analytics.md
+++ b/doc/user/analytics/value_stream_analytics.md
@@ -18,9 +18,6 @@ spent in each stage defined in the process.
For information on how to contribute to the development of Value Stream Analytics, see our [contributor documentation](../../development/value_stream_analytics.md).
-NOTE: **Note:**
-Use the `cycle_analytics` feature flag to enable at the group level.
-
Value Stream Analytics is useful in order to quickly determine the velocity of a given
project. It points to bottlenecks in the development process, enabling management
to uncover, triage, and identify the root cause of slowdowns in the software development life cycle.
@@ -33,7 +30,7 @@ calculates a separate median for each stage.
Value Stream Analytics is available:
- From GitLab 12.9, at the group level via **Group > Analytics > Value Stream**. **(PREMIUM)**
-- At the project level via **Project > Value Stream Analytics**.
+- At the project level via **Project > Analytics > Value Stream**.
There are seven stages that are tracked as part of the Value Stream Analytics calculations.
@@ -300,15 +297,6 @@ toggled to show data for merge requests and further refined for specific group-l
By default the top group-level labels (max. 10) are pre-selected, with the ability to
select up to a total of 15 labels.
-### Disabling chart
-
-This chart is enabled by default. If you have a self-managed instance, an
-administrator can open a Rails console and disable it with the following command:
-
-```ruby
-Feature.disable(:tasks_by_type_chart)
-```
-
## Permissions
The current permissions on the Project Value Stream Analytics dashboard are:
@@ -331,14 +319,6 @@ For Value Stream Analytics functionality introduced in GitLab 12.3 and later:
- Features are available only on
[Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
-## Troubleshooting
-
-If you see an error as listed in the following table, try the noted solution:
-
-| Error | Solution |
-|---------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| There was an error fetching the top labels. | Manually enable tasks by type feature in the [rails console](../../administration/troubleshooting/navigating_gitlab_via_rails_console.md#starting-a-rails-console-session), specifically `Feature.enable(:tasks_by_type_chart)`. |
-
## More resources
Learn more about Value Stream Analytics in the following resources:
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 5ce3353b734..2561be148ac 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -179,6 +179,14 @@ module API
end
end
+ def find_tag!(tag_name)
+ if Gitlab::GitRefValidator.validate(tag_name)
+ user_project.repository.find_tag(tag_name) || not_found!('Tag')
+ else
+ render_api_error!('The tag refname is invalid', 400)
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def find_project_issue(iid, project_id = nil)
project = project_id ? find_project!(project_id) : user_project
diff --git a/lib/gitlab/git/attributes_parser.rb b/lib/gitlab/git/attributes_parser.rb
index 8b9d74ae8e7..630b1aba2f5 100644
--- a/lib/gitlab/git/attributes_parser.rb
+++ b/lib/gitlab/git/attributes_parser.rb
@@ -85,6 +85,8 @@ module Gitlab
yield line.strip
end
+ # Catch invalid byte sequences
+ rescue ArgumentError
end
private
diff --git a/lib/gitlab/graphql/pagination/externally_paginated_array_connection.rb b/lib/gitlab/graphql/pagination/externally_paginated_array_connection.rb
index 1f01dd07571..12e047420bf 100644
--- a/lib/gitlab/graphql/pagination/externally_paginated_array_connection.rb
+++ b/lib/gitlab/graphql/pagination/externally_paginated_array_connection.rb
@@ -23,6 +23,20 @@ module Gitlab
alias_method :has_next_page, :next_page?
alias_method :has_previous_page, :previous_page?
+
+ private
+
+ def load_nodes
+ @nodes ||= begin
+ # As the pagination happens externally we just grab all the nodes
+ limited_nodes = items
+
+ limited_nodes = limited_nodes.first(first) if first
+ limited_nodes = limited_nodes.last(last) if last
+
+ limited_nodes
+ end
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index b339c52ce09..80100b281dd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -23340,7 +23340,7 @@ msgstr ""
msgid "Value Stream Analytics gives an overview of how much time it takes to go from idea to production in your project."
msgstr ""
-msgid "Var"
+msgid "Variable"
msgstr ""
msgid "Variable will be masked in job logs."
diff --git a/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
new file mode 100644
index 00000000000..30d2b79a92f
--- /dev/null
+++ b/spec/controllers/projects/design_management/designs/raw_images_controller_spec.rb
@@ -0,0 +1,153 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::DesignManagement::Designs::RawImagesController do
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:viewer) { issue.author }
+ let(:design_id) { design.id }
+ let(:sha) { design.versions.first.sha }
+ let(:filename) { design.filename }
+
+ before do
+ enable_design_management
+ end
+
+ describe 'GET #show' do
+ subject do
+ get(:show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ design_id: design_id,
+ sha: sha
+ })
+ end
+
+ before do
+ sign_in(viewer)
+ end
+
+ context 'when the design is not an LFS file' do
+ let_it_be(:design) { create(:design, :with_file, issue: issue, versions_count: 2) }
+
+ # For security, .svg images should only ever be served with Content-Disposition: attachment.
+ # If this specs ever fails we must assess whether we should be serving svg images.
+ # See https://gitlab.com/gitlab-org/gitlab/issues/12771
+ it 'serves files with `Content-Disposition: attachment`' do
+ subject
+
+ expect(response.header['Content-Disposition']).to eq('attachment')
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'serves files with Workhorse' do
+ subject
+
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq "true"
+ expect(response.header[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with('git-blob:')
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it_behaves_like 'project cache control headers'
+
+ context 'when the user does not have permission' do
+ let_it_be(:viewer) { create(:user) }
+
+ specify do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when design does not exist' do
+ let(:design_id) { 'foo' }
+
+ specify do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'sha param' do
+ let(:newest_version) { design.versions.ordered.first }
+ let(:oldest_version) { design.versions.ordered.last }
+
+ shared_examples 'a successful request for sha' do
+ it do
+ expect_next_instance_of(DesignManagement::Repository) do |repository|
+ expect(repository).to receive(:blob_at).with(expected_ref, design.full_path).and_call_original
+ end
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ specify { expect(newest_version.sha).not_to eq(oldest_version.sha) }
+
+ context 'when sha is the newest version sha' do
+ let(:sha) { newest_version.sha }
+ let(:expected_ref) { sha }
+
+ it_behaves_like 'a successful request for sha'
+ end
+
+ context 'when sha is the oldest version sha' do
+ let(:sha) { oldest_version.sha }
+ let(:expected_ref) { sha }
+
+ it_behaves_like 'a successful request for sha'
+ end
+
+ context 'when sha is nil' do
+ let(:sha) { nil }
+ let(:expected_ref) { 'master' }
+
+ it_behaves_like 'a successful request for sha'
+ end
+ end
+ end
+
+ context 'when the design is an LFS file' do
+ let_it_be(:design) { create(:design, :with_lfs_file, issue: issue) }
+
+ # For security, .svg images should only ever be served with Content-Disposition: attachment.
+ # If this specs ever fails we must assess whether we should be serving svg images.
+ # See https://gitlab.com/gitlab-org/gitlab/issues/12771
+ it 'serves files with `Content-Disposition: attachment`' do
+ subject
+
+ expect(response.header['Content-Disposition']).to eq(%Q(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename}))
+ end
+
+ it 'sets appropriate caching headers' do
+ subject
+
+ expect(response.header['ETag']).to be_present
+ expect(response.header['Cache-Control']).to eq("max-age=60, private")
+ end
+ end
+
+ # Pass `skip_lfs_disabled_tests: true` to this shared example to disable
+ # the test scenarios for when LFS is disabled globally.
+ #
+ # When LFS is disabled then the design management feature also becomes disabled.
+ # When the feature is disabled, the `authorize :read_design` check within the
+ # controller will never authorize the user. Therefore #show will return a 403 and
+ # we cannot test the data that it serves.
+ it_behaves_like 'a controller that can serve LFS files', skip_lfs_disabled_tests: true do
+ let(:file) { fixture_file_upload('spec/fixtures/dk.png', '`/png') }
+ let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file.read) }
+ let(:design) { create(:design, :with_lfs_file, file: lfs_pointer.pointer, issue: issue) }
+ let(:lfs_oid) { project.design_repository.blob_at('HEAD', design.full_path).lfs_oid }
+ let(:filepath) { design.full_path }
+ end
+ end
+end
diff --git a/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
new file mode 100644
index 00000000000..9a3fee5b43a
--- /dev/null
+++ b/spec/controllers/projects/design_management/designs/resized_image_controller_spec.rb
@@ -0,0 +1,158 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::DesignManagement::Designs::ResizedImageController do
+ include DesignManagementTestHelpers
+
+ let_it_be(:project) { create(:project, :private) }
+ let_it_be(:issue) { create(:issue, project: project) }
+ let_it_be(:viewer) { issue.author }
+ let_it_be(:size) { :v432x230 }
+ let(:design) { create(:design, :with_smaller_image_versions, issue: issue, versions_count: 2) }
+ let(:design_id) { design.id }
+ let(:sha) { design.versions.first.sha }
+
+ before do
+ # TODO these tests are being temporarily skipped unless run in EE,
+ # as we are in the process of moving Design Management to FOSS in 13.0
+ # in steps. In the current step the services have not yet been moved,
+ # and the `design` factory used in this test uses the `:with_smaller_image_versions`
+ # trait, which calls `GenerateImageVersionsService` to generate the
+ # smaller image versions.
+ #
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283.
+ skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
+
+ enable_design_management
+ end
+
+ describe 'GET #show' do
+ subject do
+ get(:show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ design_id: design_id,
+ sha: sha,
+ id: size
+ })
+ end
+
+ before do
+ sign_in(viewer)
+ subject
+ end
+
+ context 'when the user does not have permission' do
+ let_it_be(:viewer) { create(:user) }
+
+ specify do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'Response headers' do
+ it 'completes the request successfully' do
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'sets Content-Disposition as attachment' do
+ filename = design.filename
+
+ expect(response.header['Content-Disposition']).to eq(%Q(attachment; filename=\"#{filename}\"; filename*=UTF-8''#{filename}))
+ end
+
+ it 'serves files with Workhorse' do
+ expect(response.header[Gitlab::Workhorse::DETECT_HEADER]).to eq 'true'
+ end
+
+ it 'sets appropriate caching headers' do
+ expect(response.header['Cache-Control']).to eq('private')
+ expect(response.header['ETag']).to be_present
+ end
+ end
+
+ context 'when design does not exist' do
+ let(:design_id) { 'foo' }
+
+ specify do
+ subject
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'when size is invalid' do
+ let_it_be(:size) { :foo }
+
+ it 'returns a 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ describe 'sha param' do
+ let(:newest_version) { design.versions.ordered.first }
+ let(:oldest_version) { design.versions.ordered.last }
+
+ # The design images generated by Factorybot are identical, so
+ # refer to the `ETag` header, which is uniquely generated from the Action
+ # (the record that represents the design at a specific version), to
+ # verify that the correct file is being returned.
+ def etag(action)
+ ActionDispatch::TestResponse.new.send(:generate_weak_etag, [action.cache_key, ''])
+ end
+
+ specify { expect(newest_version.sha).not_to eq(oldest_version.sha) }
+
+ context 'when sha is the newest version sha' do
+ let(:sha) { newest_version.sha }
+
+ it 'serves the newest image' do
+ action = newest_version.actions.first
+
+ expect(response.header['ETag']).to eq(etag(action))
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when sha is the oldest version sha' do
+ let(:sha) { oldest_version.sha }
+
+ it 'serves the oldest image' do
+ action = oldest_version.actions.first
+
+ expect(response.header['ETag']).to eq(etag(action))
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when sha is nil' do
+ let(:sha) { nil }
+
+ it 'serves the newest image' do
+ action = newest_version.actions.first
+
+ expect(response.header['ETag']).to eq(etag(action))
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
+
+ context 'when sha is not a valid version sha' do
+ let(:sha) { '570e7b2abdd848b95f2f578043fc23bd6f6fd24d' }
+
+ it 'returns a 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+
+ context 'when design does not have a smaller image size available' do
+ let(:design) { create(:design, :with_file, issue: issue) }
+
+ it 'returns a 404' do
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index 053932944bb..a22dc77997b 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1719,6 +1719,33 @@ describe Projects::IssuesController do
end
end
+ describe 'GET #designs' do
+ context 'when project has moved' do
+ let(:new_project) { create(:project) }
+ let(:issue) { create(:issue, project: new_project) }
+
+ before do
+ sign_in(user)
+
+ project.route.destroy
+ new_project.redirect_routes.create!(path: project.full_path)
+ new_project.add_developer(user)
+ end
+
+ it 'redirects from an old issue/designs correctly' do
+ get :designs,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project,
+ id: issue
+ }
+
+ expect(response).to redirect_to(designs_project_issue_path(new_project, issue))
+ expect(response).to have_gitlab_http_status(:found)
+ end
+ end
+ end
+
context 'private project with token authentication' do
let(:private_project) { create(:project, :private) }
diff --git a/spec/frontend/alert_management/components/alert_management_list_spec.js b/spec/frontend/alert_management/components/alert_management_list_spec.js
index cb2531663bd..d7170e71a96 100644
--- a/spec/frontend/alert_management/components/alert_management_list_spec.js
+++ b/spec/frontend/alert_management/components/alert_management_list_spec.js
@@ -9,6 +9,7 @@ import {
GlIcon,
GlTab,
} from '@gitlab/ui';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import AlertManagementList from '~/alert_management/components/alert_management_list.vue';
import { ALERTS_STATUS_TABS } from '../../../../app/assets/javascripts/alert_management/constants';
@@ -24,6 +25,7 @@ describe('AlertManagementList', () => {
const findStatusDropdown = () => wrapper.find(GlNewDropdown);
const findStatusFilterTabs = () => wrapper.findAll(GlTab);
const findNumberOfAlertsBadge = () => wrapper.findAll(GlBadge);
+ const findDateFields = () => wrapper.findAll(TimeAgo);
function mountComponent({
props = {
@@ -198,5 +200,45 @@ describe('AlertManagementList', () => {
).toBe(true);
});
});
+
+ describe('handle date fields', () => {
+ it('should display time ago dates when values provided', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: {
+ alerts: [
+ {
+ iid: 1,
+ startedAt: '2020-03-17T23:18:14.996Z',
+ endedAt: '2020-04-17T23:18:14.996Z',
+ severity: 'high',
+ },
+ ],
+ errored: false,
+ },
+ loading: false,
+ });
+ expect(findDateFields().length).toBe(2);
+ });
+
+ it('should not display time ago dates when values not provided', () => {
+ mountComponent({
+ props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
+ data: {
+ alerts: [
+ {
+ iid: 1,
+ startedAt: null,
+ endedAt: null,
+ severity: 'high',
+ },
+ ],
+ errored: false,
+ },
+ loading: false,
+ });
+ expect(findDateFields().exists()).toBe(false);
+ });
+ });
});
});
diff --git a/spec/frontend/ci_variable_list/services/mock_data.js b/spec/frontend/ci_variable_list/services/mock_data.js
index 09c6cd9de21..7dab33050d9 100644
--- a/spec/frontend/ci_variable_list/services/mock_data.js
+++ b/spec/frontend/ci_variable_list/services/mock_data.js
@@ -8,7 +8,7 @@ export default {
protected: false,
secret_value: 'test_val',
value: 'test_val',
- variable_type: 'Var',
+ variable_type: 'Variable',
},
],
@@ -44,7 +44,7 @@ export default {
protected: false,
secret_value: 'test_val',
value: 'test_val',
- variable_type: 'Var',
+ variable_type: 'Variable',
},
{
environment_scope: 'All (default)',
@@ -104,7 +104,7 @@ export default {
id: 28,
key: 'goku_var',
value: 'goku_val',
- variable_type: 'Var',
+ variable_type: 'Variable',
protected: true,
masked: true,
environment_scope: 'staging',
@@ -114,7 +114,7 @@ export default {
id: 25,
key: 'test_var_4',
value: 'test_val_4',
- variable_type: 'Var',
+ variable_type: 'Variable',
protected: false,
masked: false,
environment_scope: 'production',
@@ -134,7 +134,7 @@ export default {
id: 24,
key: 'test_var_3',
value: 'test_val_3',
- variable_type: 'Var',
+ variable_type: 'Variable',
protected: false,
masked: false,
environment_scope: 'All (default)',
@@ -144,7 +144,7 @@ export default {
id: 26,
key: 'test_var_5',
value: 'test_val_5',
- variable_type: 'Var',
+ variable_type: 'Variable',
protected: false,
masked: false,
environment_scope: 'production',
diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js
index 8652359f3df..ce0792d0353 100644
--- a/spec/frontend/ci_variable_list/store/mutations_spec.js
+++ b/spec/frontend/ci_variable_list/store/mutations_spec.js
@@ -47,7 +47,7 @@ describe('CI variable list mutations', () => {
describe('CLEAR_MODAL', () => {
it('should clear modal state ', () => {
const modalState = {
- variable_type: 'Var',
+ variable_type: 'Variable',
key: '',
secret_value: '',
protected: false,
diff --git a/spec/frontend/ide/services/index_spec.js b/spec/frontend/ide/services/index_spec.js
index 658ad37d7f2..f4d4122bd5a 100644
--- a/spec/frontend/ide/services/index_spec.js
+++ b/spec/frontend/ide/services/index_spec.js
@@ -221,4 +221,37 @@ describe('IDE services', () => {
});
});
});
+
+ describe('getFiles', () => {
+ let mock;
+ let relativeUrlRoot;
+ const TEST_RELATIVE_URL_ROOT = 'blah-blah';
+
+ beforeEach(() => {
+ jest.spyOn(axios, 'get');
+ relativeUrlRoot = gon.relative_url_root;
+ gon.relative_url_root = TEST_RELATIVE_URL_ROOT;
+
+ mock = new MockAdapter(axios);
+
+ mock
+ .onGet(`${TEST_RELATIVE_URL_ROOT}/${TEST_PROJECT_ID}/-/files/${TEST_COMMIT_SHA}`)
+ .reply(200, [TEST_FILE_PATH]);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ gon.relative_url_root = relativeUrlRoot;
+ });
+
+ it('initates the api call based on the passed path and commit hash', () => {
+ return services.getFiles(TEST_PROJECT_ID, TEST_COMMIT_SHA).then(({ data }) => {
+ expect(axios.get).toHaveBeenCalledWith(
+ `${gon.relative_url_root}/${TEST_PROJECT_ID}/-/files/${TEST_COMMIT_SHA}`,
+ expect.any(Object),
+ );
+ expect(data).toEqual([TEST_FILE_PATH]);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/image_diff/helpers/badge_helper_spec.js b/spec/frontend/image_diff/helpers/badge_helper_spec.js
index b3001d45e3c..c970ccc535d 100644
--- a/spec/javascripts/image_diff/helpers/badge_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/badge_helper_spec.js
@@ -66,7 +66,7 @@ describe('badge helper', () => {
});
it('should set the badge text', () => {
- expect(buttonEl.innerText).toEqual(badgeText);
+ expect(buttonEl.textContent).toEqual(badgeText);
});
it('should set the button coordinates', () => {
@@ -120,7 +120,7 @@ describe('badge helper', () => {
});
it('should update badge number', () => {
- expect(avatarBadgeEl.innerText).toEqual(badgeNumber.toString());
+ expect(avatarBadgeEl.textContent).toEqual(badgeNumber.toString());
});
it('should remove hidden class', () => {
diff --git a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
index 8e3e7f1222e..395bb7de362 100644
--- a/spec/javascripts/image_diff/helpers/comment_indicator_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/comment_indicator_helper_spec.js
@@ -128,8 +128,8 @@ describe('commentIndicatorHelper', () => {
currentTarget: containerEl.querySelector('button'),
};
- spyOn(event, 'stopPropagation');
- spyOn(textAreaEl, 'focus');
+ jest.spyOn(event, 'stopPropagation').mockImplementation(() => {});
+ jest.spyOn(textAreaEl, 'focus').mockImplementation(() => {});
commentIndicatorHelper.commentIndicatorOnClick(event);
});
diff --git a/spec/javascripts/image_diff/helpers/dom_helper_spec.js b/spec/frontend/image_diff/helpers/dom_helper_spec.js
index ffe712af2dd..9357d626bbe 100644
--- a/spec/javascripts/image_diff/helpers/dom_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/dom_helper_spec.js
@@ -44,7 +44,7 @@ describe('domHelper', () => {
});
it('should update avatar badge number', () => {
- expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString());
+ expect(discussionEl.querySelector('.badge').textContent).toEqual(badgeNumber.toString());
});
});
@@ -60,7 +60,7 @@ describe('domHelper', () => {
});
it('should update discussion badge number', () => {
- expect(discussionEl.querySelector('.badge').innerText).toEqual(badgeNumber.toString());
+ expect(discussionEl.querySelector('.badge').textContent).toEqual(badgeNumber.toString());
});
});
diff --git a/spec/javascripts/image_diff/helpers/utils_helper_spec.js b/spec/frontend/image_diff/helpers/utils_helper_spec.js
index 3b6378be883..3b6378be883 100644
--- a/spec/javascripts/image_diff/helpers/utils_helper_spec.js
+++ b/spec/frontend/image_diff/helpers/utils_helper_spec.js
diff --git a/spec/javascripts/image_diff/image_badge_spec.js b/spec/frontend/image_diff/image_badge_spec.js
index a1589d7b7a0..a11b50ead47 100644
--- a/spec/javascripts/image_diff/image_badge_spec.js
+++ b/spec/frontend/image_diff/image_badge_spec.js
@@ -71,7 +71,7 @@ describe('ImageBadge', () => {
describe('imageEl property is provided and not browser property', () => {
beforeEach(() => {
- spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(true);
+ jest.spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').mockReturnValue(true);
});
it('should generate browser property', () => {
diff --git a/spec/javascripts/image_diff/image_diff_spec.js b/spec/frontend/image_diff/image_diff_spec.js
index 21e7b8e2e9b..c15718b5106 100644
--- a/spec/javascripts/image_diff/image_diff_spec.js
+++ b/spec/frontend/image_diff/image_diff_spec.js
@@ -75,7 +75,7 @@ describe('ImageDiff', () => {
describe('init', () => {
beforeEach(() => {
- spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ jest.spyOn(ImageDiff.prototype, 'bindEvents').mockImplementation(() => {});
imageDiff = new ImageDiff(element);
imageDiff.init();
});
@@ -97,19 +97,19 @@ describe('ImageDiff', () => {
let imageEl;
beforeEach(() => {
- spyOn(imageDiffHelper, 'toggleCollapsed').and.callFake(() => {});
- spyOn(imageDiffHelper, 'commentIndicatorOnClick').and.callFake(() => {});
- spyOn(imageDiffHelper, 'removeCommentIndicator').and.callFake(() => {});
- spyOn(ImageDiff.prototype, 'imageClicked').and.callFake(() => {});
- spyOn(ImageDiff.prototype, 'addBadge').and.callFake(() => {});
- spyOn(ImageDiff.prototype, 'removeBadge').and.callFake(() => {});
- spyOn(ImageDiff.prototype, 'renderBadges').and.callFake(() => {});
+ jest.spyOn(imageDiffHelper, 'toggleCollapsed').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'commentIndicatorOnClick').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'removeCommentIndicator').mockImplementation(() => {});
+ jest.spyOn(ImageDiff.prototype, 'imageClicked').mockImplementation(() => {});
+ jest.spyOn(ImageDiff.prototype, 'addBadge').mockImplementation(() => {});
+ jest.spyOn(ImageDiff.prototype, 'removeBadge').mockImplementation(() => {});
+ jest.spyOn(ImageDiff.prototype, 'renderBadges').mockImplementation(() => {});
imageEl = element.querySelector('.diff-file .js-image-frame img');
});
describe('default', () => {
beforeEach(() => {
- spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ jest.spyOn(imageUtility, 'isImageLoaded').mockReturnValue(false);
imageDiff = new ImageDiff(element);
imageDiff.imageEl = imageEl;
imageDiff.bindEvents();
@@ -130,7 +130,7 @@ describe('ImageDiff', () => {
describe('image not loaded', () => {
beforeEach(() => {
- spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ jest.spyOn(imageUtility, 'isImageLoaded').mockReturnValue(false);
imageDiff = new ImageDiff(element);
imageDiff.imageEl = imageEl;
imageDiff.bindEvents();
@@ -146,7 +146,7 @@ describe('ImageDiff', () => {
describe('canCreateNote', () => {
beforeEach(() => {
- spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ jest.spyOn(imageUtility, 'isImageLoaded').mockReturnValue(false);
imageDiff = new ImageDiff(element, {
canCreateNote: true,
});
@@ -185,7 +185,7 @@ describe('ImageDiff', () => {
describe('canCreateNote is false', () => {
beforeEach(() => {
- spyOn(imageUtility, 'isImageLoaded').and.returnValue(false);
+ jest.spyOn(imageUtility, 'isImageLoaded').mockReturnValue(false);
imageDiff = new ImageDiff(element);
imageDiff.imageEl = imageEl;
imageDiff.bindEvents();
@@ -202,12 +202,12 @@ describe('ImageDiff', () => {
describe('imageClicked', () => {
beforeEach(() => {
- spyOn(imageDiffHelper, 'getTargetSelection').and.returnValue({
+ jest.spyOn(imageDiffHelper, 'getTargetSelection').mockReturnValue({
actual: {},
browser: {},
});
- spyOn(imageDiffHelper, 'setPositionDataAttribute').and.callFake(() => {});
- spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {});
+ jest.spyOn(imageDiffHelper, 'setPositionDataAttribute').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'showCommentIndicator').mockImplementation(() => {});
imageDiff = new ImageDiff(element);
imageDiff.imageClicked({
detail: {
@@ -231,7 +231,7 @@ describe('ImageDiff', () => {
describe('renderBadges', () => {
beforeEach(() => {
- spyOn(ImageDiff.prototype, 'renderBadge').and.callFake(() => {});
+ jest.spyOn(ImageDiff.prototype, 'renderBadge').mockImplementation(() => {});
imageDiff = new ImageDiff(element);
imageDiff.renderBadges();
});
@@ -239,7 +239,7 @@ describe('ImageDiff', () => {
it('should call renderBadge for each discussionEl', () => {
const discussionEls = element.querySelectorAll('.note-container .discussion-notes .notes');
- expect(imageDiff.renderBadge.calls.count()).toEqual(discussionEls.length);
+ expect(imageDiff.renderBadge.mock.calls.length).toEqual(discussionEls.length);
});
});
@@ -247,9 +247,9 @@ describe('ImageDiff', () => {
let discussionEls;
beforeEach(() => {
- spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {});
- spyOn(imageDiffHelper, 'addImageCommentBadge').and.callFake(() => {});
- spyOn(imageDiffHelper, 'generateBadgeFromDiscussionDOM').and.returnValue({
+ jest.spyOn(imageDiffHelper, 'addImageBadge').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'addImageCommentBadge').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'generateBadgeFromDiscussionDOM').mockReturnValue({
browser: {},
noteId: 'noteId',
});
@@ -282,9 +282,9 @@ describe('ImageDiff', () => {
describe('addBadge', () => {
beforeEach(() => {
- spyOn(imageDiffHelper, 'addImageBadge').and.callFake(() => {});
- spyOn(imageDiffHelper, 'addAvatarBadge').and.callFake(() => {});
- spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {});
+ jest.spyOn(imageDiffHelper, 'addImageBadge').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'addAvatarBadge').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').mockImplementation(() => {});
imageDiff = new ImageDiff(element);
imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame');
imageDiff.addBadge({
@@ -320,8 +320,8 @@ describe('ImageDiff', () => {
beforeEach(() => {
const { imageMeta } = mockData;
- spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').and.callFake(() => {});
- spyOn(imageDiffHelper, 'updateDiscussionAvatarBadgeNumber').and.callFake(() => {});
+ jest.spyOn(imageDiffHelper, 'updateDiscussionBadgeNumber').mockImplementation(() => {});
+ jest.spyOn(imageDiffHelper, 'updateDiscussionAvatarBadgeNumber').mockImplementation(() => {});
imageDiff = new ImageDiff(element);
imageDiff.imageBadges = [imageMeta, imageMeta, imageMeta];
imageDiff.imageFrameEl = element.querySelector('.diff-file .js-image-frame');
@@ -336,8 +336,8 @@ describe('ImageDiff', () => {
it('should update next imageBadgeEl value', () => {
const imageBadgeEls = imageDiff.imageFrameEl.querySelectorAll('.badge');
- expect(imageBadgeEls[0].innerText).toEqual('1');
- expect(imageBadgeEls[1].innerText).toEqual('2');
+ expect(imageBadgeEls[0].textContent).toEqual('1');
+ expect(imageBadgeEls[1].textContent).toEqual('2');
expect(imageBadgeEls.length).toEqual(2);
});
diff --git a/spec/javascripts/image_diff/mock_data.js b/spec/frontend/image_diff/mock_data.js
index a0d1732dd0a..a0d1732dd0a 100644
--- a/spec/javascripts/image_diff/mock_data.js
+++ b/spec/frontend/image_diff/mock_data.js
diff --git a/spec/javascripts/image_diff/replaced_image_diff_spec.js b/spec/frontend/image_diff/replaced_image_diff_spec.js
index 62e7c8b6c6a..f2a7b7f8406 100644
--- a/spec/javascripts/image_diff/replaced_image_diff_spec.js
+++ b/spec/frontend/image_diff/replaced_image_diff_spec.js
@@ -76,8 +76,8 @@ describe('ReplacedImageDiff', () => {
describe('init', () => {
beforeEach(() => {
- spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {});
- spyOn(ReplacedImageDiff.prototype, 'generateImageEls').and.callFake(() => {});
+ jest.spyOn(ReplacedImageDiff.prototype, 'bindEvents').mockImplementation(() => {});
+ jest.spyOn(ReplacedImageDiff.prototype, 'generateImageEls').mockImplementation(() => {});
replacedImageDiff = new ReplacedImageDiff(element);
replacedImageDiff.init();
@@ -140,7 +140,7 @@ describe('ReplacedImageDiff', () => {
describe('generateImageEls', () => {
beforeEach(() => {
- spyOn(ReplacedImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ jest.spyOn(ReplacedImageDiff.prototype, 'bindEvents').mockImplementation(() => {});
replacedImageDiff = new ReplacedImageDiff(element, {
canCreateNote: false,
@@ -163,7 +163,7 @@ describe('ReplacedImageDiff', () => {
describe('bindEvents', () => {
beforeEach(() => {
- spyOn(ImageDiff.prototype, 'bindEvents').and.callFake(() => {});
+ jest.spyOn(ImageDiff.prototype, 'bindEvents').mockImplementation(() => {});
replacedImageDiff = new ReplacedImageDiff(element);
setupViewModesEls();
@@ -176,7 +176,7 @@ describe('ReplacedImageDiff', () => {
});
it('should register click eventlistener to 2-up view mode', done => {
- spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake(viewMode => {
+ jest.spyOn(ReplacedImageDiff.prototype, 'changeView').mockImplementation(viewMode => {
expect(viewMode).toEqual(viewTypes.TWO_UP);
done();
});
@@ -186,7 +186,7 @@ describe('ReplacedImageDiff', () => {
});
it('should register click eventlistener to swipe view mode', done => {
- spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake(viewMode => {
+ jest.spyOn(ReplacedImageDiff.prototype, 'changeView').mockImplementation(viewMode => {
expect(viewMode).toEqual(viewTypes.SWIPE);
done();
});
@@ -196,7 +196,7 @@ describe('ReplacedImageDiff', () => {
});
it('should register click eventlistener to onion skin view mode', done => {
- spyOn(ReplacedImageDiff.prototype, 'changeView').and.callFake(viewMode => {
+ jest.spyOn(ReplacedImageDiff.prototype, 'changeView').mockImplementation(viewMode => {
expect(viewMode).toEqual(viewTypes.SWIPE);
done();
});
@@ -247,7 +247,7 @@ describe('ReplacedImageDiff', () => {
describe('changeView', () => {
beforeEach(() => {
replacedImageDiff = new ReplacedImageDiff(element);
- spyOn(imageDiffHelper, 'removeCommentIndicator').and.returnValue({
+ jest.spyOn(imageDiffHelper, 'removeCommentIndicator').mockReturnValue({
removed: false,
});
setupImageFrameEls();
@@ -265,13 +265,12 @@ describe('ReplacedImageDiff', () => {
describe('valid viewType', () => {
beforeEach(() => {
- jasmine.clock().install();
- spyOn(ReplacedImageDiff.prototype, 'renderNewView').and.callFake(() => {});
+ jest.spyOn(ReplacedImageDiff.prototype, 'renderNewView').mockImplementation(() => {});
replacedImageDiff.changeView(viewTypes.ONION_SKIN);
});
afterEach(() => {
- jasmine.clock().uninstall();
+ jest.clearAllTimers();
});
it('should call removeCommentIndicator', () => {
@@ -287,7 +286,7 @@ describe('ReplacedImageDiff', () => {
});
it('should call renderNewView', () => {
- jasmine.clock().tick(251);
+ jest.advanceTimersByTime(251);
expect(replacedImageDiff.renderNewView).toHaveBeenCalled();
});
@@ -300,7 +299,7 @@ describe('ReplacedImageDiff', () => {
});
it('should call renderBadges', () => {
- spyOn(ReplacedImageDiff.prototype, 'renderBadges').and.callFake(() => {});
+ jest.spyOn(ReplacedImageDiff.prototype, 'renderBadges').mockImplementation(() => {});
replacedImageDiff.renderNewView({
removed: false,
@@ -326,14 +325,16 @@ describe('ReplacedImageDiff', () => {
});
it('should pass showCommentIndicator normalized indicator values', done => {
- spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(() => {});
- spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.callFake((imageEl, meta) => {
- expect(meta.x).toEqual(indicator.x);
- expect(meta.y).toEqual(indicator.y);
- expect(meta.width).toEqual(indicator.image.width);
- expect(meta.height).toEqual(indicator.image.height);
- done();
- });
+ jest.spyOn(imageDiffHelper, 'showCommentIndicator').mockImplementation(() => {});
+ jest
+ .spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement')
+ .mockImplementation((imageEl, meta) => {
+ expect(meta.x).toEqual(indicator.x);
+ expect(meta.y).toEqual(indicator.y);
+ expect(meta.width).toEqual(indicator.image.width);
+ expect(meta.height).toEqual(indicator.image.height);
+ done();
+ });
replacedImageDiff.renderNewView(indicator);
});
@@ -341,13 +342,13 @@ describe('ReplacedImageDiff', () => {
const normalized = {
normalized: true,
};
- spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').and.returnValue(normalized);
- spyOn(imageDiffHelper, 'showCommentIndicator').and.callFake(
- (imageFrameEl, normalizedIndicator) => {
+ jest.spyOn(imageDiffHelper, 'resizeCoordinatesToImageElement').mockReturnValue(normalized);
+ jest
+ .spyOn(imageDiffHelper, 'showCommentIndicator')
+ .mockImplementation((imageFrameEl, normalizedIndicator) => {
expect(normalizedIndicator).toEqual(normalized);
done();
- },
- );
+ });
replacedImageDiff.renderNewView(indicator);
});
});
diff --git a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
index 367e07d3ad3..f656bb0b60d 100644
--- a/spec/javascripts/vue_shared/components/ci_badge_link_spec.js
+++ b/spec/frontend/vue_shared/components/ci_badge_link_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import ciBadge from '~/vue_shared/components/ci_badge_link.vue';
describe('CI Badge Link Component', () => {
diff --git a/spec/javascripts/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js
index 9486d7d4f23..63afe631063 100644
--- a/spec/javascripts/vue_shared/components/ci_icon_spec.js
+++ b/spec/frontend/vue_shared/components/ci_icon_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import ciIcon from '~/vue_shared/components/ci_icon.vue';
describe('CI Icon component', () => {
diff --git a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js b/spec/frontend/vue_shared/components/content_viewer/content_viewer_spec.js
index fbe9337ecf4..b0563f2f6de 100644
--- a/spec/javascripts/vue_shared/components/content_viewer/content_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/content_viewer/content_viewer_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import MockAdapter from 'axios-mock-adapter';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import waitForPromises from 'spec/helpers/wait_for_promises';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import waitForPromises from 'helpers/wait_for_promises';
import { GREEN_BOX_IMAGE_URL } from 'spec/test_constants';
import axios from '~/lib/utils/axios_utils';
import contentViewer from '~/vue_shared/components/content_viewer/content_viewer.vue';
@@ -96,7 +96,7 @@ describe('ContentViewer', () => {
it('markdown preview receives the file path as a parameter', done => {
mock = new MockAdapter(axios);
- spyOn(axios, 'post').and.callThrough();
+ jest.spyOn(axios, 'post');
mock.onPost(`${gon.relative_url_root}/testproject/preview_markdown`).reply(200, {
body: '<b>testing</b>',
});
@@ -114,7 +114,7 @@ describe('ContentViewer', () => {
expect(axios.post).toHaveBeenCalledWith(
`${gon.relative_url_root}/testproject/preview_markdown`,
{ path: 'foo/test.md', text: '* Test' },
- jasmine.any(Object),
+ expect.any(Object),
);
})
.then(done)
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
index a8acecdd3fc..636508be6b6 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/diff_viewer_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/diff_viewer_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import { GREEN_BOX_IMAGE_URL, RED_BOX_IMAGE_URL } from 'spec/test_constants';
import diffViewer from '~/vue_shared/components/diff_viewer/diff_viewer.vue';
@@ -32,7 +32,7 @@ describe('DiffViewer', () => {
createComponent({ ...requiredProps, projectPath: '' });
- setTimeout(() => {
+ setImmediate(() => {
expect(vm.$el.querySelector('.deleted img').getAttribute('src')).toBe(
`//-/raw/DEF/${RED_BOX_IMAGE_URL}`,
);
@@ -53,7 +53,7 @@ describe('DiffViewer', () => {
oldPath: 'testold.abc',
});
- setTimeout(() => {
+ setImmediate(() => {
expect(vm.$el.querySelector('.deleted .file-info').textContent.trim()).toContain(
'testold.abc',
);
diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js
index b00fa785a0e..892a96b76fd 100644
--- a/spec/javascripts/vue_shared/components/dropdown/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_button_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
+import { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import dropdownButtonComponent from '~/vue_shared/components/dropdown/dropdown_button.vue';
const defaultLabel = 'Select';
@@ -74,7 +74,7 @@ describe('DropdownButtonComponent', () => {
},
);
- expect(vm.$el).not.toContainElement('.dropdown-toggle-text');
+ expect(vm.$el.querySelector('.dropdown-toggle-text')).toBeNull();
expect(vm.$el).toHaveText('Lorem Ipsum Dolar');
});
});
diff --git a/spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_hidden_input_spec.js
index 402de2a8788..30b8e869aab 100644
--- a/spec/javascripts/vue_shared/components/dropdown/dropdown_hidden_input_spec.js
+++ b/spec/frontend/vue_shared/components/dropdown/dropdown_hidden_input_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownHiddenInputComponent from '~/vue_shared/components/dropdown/dropdown_hidden_input.vue';
import { mockLabels } from './mock_data';
diff --git a/spec/javascripts/vue_shared/components/dropdown/mock_data.js b/spec/frontend/vue_shared/components/dropdown/mock_data.js
index b09d42da401..b09d42da401 100644
--- a/spec/javascripts/vue_shared/components/dropdown/mock_data.js
+++ b/spec/frontend/vue_shared/components/dropdown/mock_data.js
diff --git a/spec/javascripts/vue_shared/components/file_finder/item_spec.js b/spec/frontend/vue_shared/components/file_finder/item_spec.js
index e18d0a46223..63f2614106d 100644
--- a/spec/javascripts/vue_shared/components/file_finder/item_spec.js
+++ b/spec/frontend/vue_shared/components/file_finder/item_spec.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
-import { file } from 'spec/ide/helpers';
+import { file } from 'jest/ide/helpers';
import ItemComponent from '~/vue_shared/components/file_finder/item.vue';
-import createComponent from '../../../helpers/vue_mount_component_helper';
+import createComponent from 'helpers/vue_mount_component_helper';
describe('File finder item spec', () => {
const Component = Vue.extend(ItemComponent);
@@ -75,7 +75,7 @@ describe('File finder item spec', () => {
});
it('emits event when clicked', () => {
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.$el.click();
diff --git a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js b/spec/frontend/vue_shared/components/filtered_search_dropdown_spec.js
index 0bb4a04557b..87cafa0bb8c 100644
--- a/spec/javascripts/vue_shared/components/filtered_search_dropdown_spec.js
+++ b/spec/frontend/vue_shared/components/filtered_search_dropdown_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import component from '~/vue_shared/components/filtered_search_dropdown.vue';
describe('Filtered search dropdown', () => {
@@ -125,7 +125,7 @@ describe('Filtered search dropdown', () => {
describe('on click create button', () => {
it('emits createItem event with the filter', done => {
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.$nextTick(() => {
vm.$el.querySelector('.js-dropdown-create-button').click();
diff --git a/spec/javascripts/vue_shared/components/gl_countdown_spec.js b/spec/frontend/vue_shared/components/gl_countdown_spec.js
index 929ffe219f4..365c9fad478 100644
--- a/spec/javascripts/vue_shared/components/gl_countdown_spec.js
+++ b/spec/frontend/vue_shared/components/gl_countdown_spec.js
@@ -1,4 +1,4 @@
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import Vue from 'vue';
import GlCountdown from '~/vue_shared/components/gl_countdown.vue';
@@ -8,13 +8,12 @@ describe('GlCountdown', () => {
let now = '2000-01-01T00:00:00Z';
beforeEach(() => {
- spyOn(Date, 'now').and.callFake(() => new Date(now).getTime());
- jasmine.clock().install();
+ jest.spyOn(Date, 'now').mockImplementation(() => new Date(now).getTime());
});
afterEach(() => {
vm.$destroy();
- jasmine.clock().uninstall();
+ jest.clearAllTimers();
});
describe('when there is time remaining', () => {
@@ -29,16 +28,16 @@ describe('GlCountdown', () => {
});
it('displays remaining time', () => {
- expect(vm.$el).toContainText('01:02:03');
+ expect(vm.$el.textContent).toContain('01:02:03');
});
it('updates remaining time', done => {
now = '2000-01-01T00:00:01Z';
- jasmine.clock().tick(1000);
+ jest.advanceTimersByTime(1000);
Vue.nextTick()
.then(() => {
- expect(vm.$el).toContainText('01:02:02');
+ expect(vm.$el.textContent).toContain('01:02:02');
done();
})
.catch(done.fail);
@@ -57,19 +56,26 @@ describe('GlCountdown', () => {
});
it('displays 00:00:00', () => {
- expect(vm.$el).toContainText('00:00:00');
+ expect(vm.$el.textContent).toContain('00:00:00');
});
});
describe('when an invalid date is passed', () => {
+ beforeEach(() => {
+ Vue.config.warnHandler = jest.fn();
+ });
+
+ afterEach(() => {
+ Vue.config.warnHandler = null;
+ });
+
it('throws a validation error', () => {
- spyOn(Vue.config, 'warnHandler').and.stub();
vm = mountComponent(Component, {
endDateString: 'this is invalid',
});
expect(Vue.config.warnHandler).toHaveBeenCalledTimes(1);
- const [errorMessage] = Vue.config.warnHandler.calls.argsFor(0);
+ const [errorMessage] = Vue.config.warnHandler.mock.calls[0];
expect(errorMessage).toMatch(/^Invalid prop: .* "endDateString"/);
});
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/frontend/vue_shared/components/header_ci_component_spec.js
index b1abc972e1d..216563165d6 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/frontend/vue_shared/components/header_ci_component_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent, { mountComponentWithSlots } from 'spec/helpers/vue_mount_component_helper';
+import mountComponent, { mountComponentWithSlots } from 'helpers/vue_mount_component_helper';
import headerCi from '~/vue_shared/components/header_ci_component.vue';
describe('Header CI Component', () => {
diff --git a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
index a87998aa72f..e7c31014bfc 100644
--- a/spec/javascripts/vue_shared/components/markdown/toolbar_spec.js
+++ b/spec/frontend/vue_shared/components/markdown/toolbar_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import toolbar from '~/vue_shared/components/markdown/toolbar.vue';
describe('toolbar', () => {
diff --git a/spec/javascripts/vue_shared/components/navigation_tabs_spec.js b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
index beb980a6556..561456d614e 100644
--- a/spec/javascripts/vue_shared/components/navigation_tabs_spec.js
+++ b/spec/frontend/vue_shared/components/navigation_tabs_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import navigationTabs from '~/vue_shared/components/navigation_tabs.vue';
describe('navigation tabs component', () => {
@@ -56,7 +56,7 @@ describe('navigation tabs component', () => {
});
it('should trigger onTabClick', () => {
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
vm.$el.querySelector('.js-pipelines-tab-pending').click();
expect(vm.$emit).toHaveBeenCalledWith('onChangeTab', 'pending');
diff --git a/spec/javascripts/vue_shared/components/pikaday_spec.js b/spec/frontend/vue_shared/components/pikaday_spec.js
index b787ba7596f..867bf88ff50 100644
--- a/spec/javascripts/vue_shared/components/pikaday_spec.js
+++ b/spec/frontend/vue_shared/components/pikaday_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import datePicker from '~/vue_shared/components/pikaday.vue';
describe('datePicker', () => {
@@ -20,7 +20,7 @@ describe('datePicker', () => {
});
it('should toggle when dropdown is clicked', () => {
- const hidePicker = jasmine.createSpy();
+ const hidePicker = jest.fn();
vm.$on('hidePicker', hidePicker);
vm.$el.querySelector('.dropdown-menu-toggle').click();
diff --git a/spec/javascripts/vue_shared/components/project_avatar/default_spec.js b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
index 2ec19ebf80e..090f8b69213 100644
--- a/spec/javascripts/vue_shared/components/project_avatar/default_spec.js
+++ b/spec/frontend/vue_shared/components/project_avatar/default_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { projectData } from 'spec/ide/mock_data';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import { projectData } from 'jest/ide/mock_data';
import { TEST_HOST } from 'spec/test_constants';
import { getFirstCharacterCapitalized } from '~/lib/utils/text_utility';
import ProjectAvatarDefault from '~/vue_shared/components/project_avatar/default.vue';
@@ -48,8 +48,8 @@ describe('ProjectAvatarDefault component', () => {
vm.$nextTick()
.then(() => {
- expect(vm.$el).toContainElement('.avatar');
- expect(vm.$el).not.toContainElement('.identicon');
+ expect(vm.$el.querySelector('.avatar')).not.toBeNull();
+ expect(vm.$el.querySelector('.identicon')).toBeNull();
expect(vm.$el.querySelector('img')).toHaveAttr('src', avatarUrl);
})
.then(done)
diff --git a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
index e73fb97b741..eb1d9e93634 100644
--- a/spec/javascripts/vue_shared/components/project_selector/project_list_item_spec.js
+++ b/spec/frontend/vue_shared/components/project_selector/project_list_item_spec.js
@@ -1,5 +1,5 @@
import { shallowMount, createLocalVue } from '@vue/test-utils';
-import { trimText } from 'spec/helpers/text_helper';
+import { trimText } from 'helpers/text_helper';
import ProjectListItem from '~/vue_shared/components/project_selector/project_list_item.vue';
const localVue = createLocalVue();
@@ -9,7 +9,7 @@ describe('ProjectListItem component', () => {
let wrapper;
let vm;
let options;
- loadJSONFixtures('static/projects.json');
+
const project = getJSONFixture('static/projects.json')[0];
beforeEach(() => {
@@ -44,7 +44,7 @@ describe('ProjectListItem component', () => {
wrapper = shallowMount(Component, options);
({ vm } = wrapper);
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
wrapper.vm.onClick();
expect(wrapper.vm.$emit).toHaveBeenCalledWith('click');
@@ -95,7 +95,7 @@ describe('ProjectListItem component', () => {
});
it('prevents search query and project name XSS', () => {
- const alertSpy = spyOn(window, 'alert');
+ const alertSpy = jest.spyOn(window, 'alert');
options.propsData.project.name = "my-xss-pro<script>alert('XSS');</script>ject";
options.propsData.matcher = "pro<script>alert('XSS');</script>";
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
index d90fafb6bf7..9db86fa775f 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/base_spec.js
@@ -4,10 +4,7 @@ import { shallowMount } from '@vue/test-utils';
import LabelsSelect from '~/labels_select';
import BaseComponent from '~/vue_shared/components/sidebar/labels_select/base.vue';
-import {
- mockConfig,
- mockLabels,
-} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockConfig, mockLabels } from './mock_data';
const createComponent = (config = mockConfig) =>
shallowMount(BaseComponent, {
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
index a4121448492..d02d924bd2b 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_button_spec.js
@@ -3,10 +3,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownButtonComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_button.vue';
-import {
- mockConfig,
- mockLabels,
-} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockConfig, mockLabels } from './mock_data';
const componentConfig = {
...mockConfig,
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
index d0299523137..edec3b138b3 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_create_label_spec.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownCreateLabelComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_create_label.vue';
-import { mockSuggestedColors } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockSuggestedColors } from './mock_data';
const createComponent = headerTitle => {
const Component = Vue.extend(dropdownCreateLabelComponent);
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
index 784bbaf8e6a..7e9e242a4f5 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_footer_spec.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownFooterComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_footer.vue';
-import { mockConfig } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockConfig } from './mock_data';
const createComponent = (
labelsWebUrl = mockConfig.labelsWebUrl,
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
index 887c04268d1..e09f0006359 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed_spec.js
@@ -3,7 +3,7 @@ import Vue from 'vue';
import mountComponent from 'helpers/vue_mount_component_helper';
import dropdownValueCollapsedComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value_collapsed.vue';
-import { mockLabels } from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockLabels } from './mock_data';
const createComponent = (labels = mockLabels) => {
const Component = Vue.extend(dropdownValueCollapsedComponent);
diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
index 06355c0dd65..c33cffb421d 100644
--- a/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/dropdown_value_spec.js
@@ -2,10 +2,7 @@ import { mount } from '@vue/test-utils';
import DropdownValueComponent from '~/vue_shared/components/sidebar/labels_select/dropdown_value.vue';
import { GlLabel } from '@gitlab/ui';
-import {
- mockConfig,
- mockLabels,
-} from '../../../../../javascripts/vue_shared/components/sidebar/labels_select/mock_data';
+import { mockConfig, mockLabels } from './mock_data';
const createComponent = (
labels = mockLabels,
diff --git a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js
index 6564c012e67..6564c012e67 100644
--- a/spec/javascripts/vue_shared/components/sidebar/labels_select/mock_data.js
+++ b/spec/frontend/vue_shared/components/sidebar/labels_select/mock_data.js
diff --git a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js b/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
index 3e044f47a3f..bc86ee5a0c6 100644
--- a/spec/javascripts/vue_shared/components/stacked_progress_bar_spec.js
+++ b/spec/frontend/vue_shared/components/stacked_progress_bar_spec.js
@@ -1,6 +1,6 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import stackedProgressBarComponent from '~/vue_shared/components/stacked_progress_bar.vue';
const createComponent = config => {
diff --git a/spec/javascripts/vue_shared/components/tabs/tab_spec.js b/spec/frontend/vue_shared/components/tabs/tab_spec.js
index 8437fe37738..8cf07a9177c 100644
--- a/spec/javascripts/vue_shared/components/tabs/tab_spec.js
+++ b/spec/frontend/vue_shared/components/tabs/tab_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import Tab from '~/vue_shared/components/tabs/tab.vue';
describe('Tab component', () => {
diff --git a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js b/spec/frontend/vue_shared/components/tabs/tabs_spec.js
index 50ba18cd338..49d92094b34 100644
--- a/spec/javascripts/vue_shared/components/tabs/tabs_spec.js
+++ b/spec/frontend/vue_shared/components/tabs/tabs_spec.js
@@ -5,28 +5,23 @@ import Tab from '~/vue_shared/components/tabs/tab.vue';
describe('Tabs component', () => {
let vm;
- beforeEach(done => {
+ beforeEach(() => {
vm = new Vue({
components: {
Tabs,
Tab,
},
- template: `
- <div>
- <tabs>
- <tab title="Testing" active>
- First tab
- </tab>
- <tab>
- <template slot="title">Test slot</template>
- Second tab
- </tab>
- </tabs>
- </div>
- `,
+ render(h) {
+ return h('div', [
+ h('tabs', [
+ h('tab', { attrs: { title: 'Testing', active: true } }, 'First tab'),
+ h('tab', [h('template', { slot: 'title' }, 'Test slot'), 'Second tab']),
+ ]),
+ ]);
+ },
}).$mount();
- setTimeout(done);
+ return vm.$nextTick();
});
describe('tab links', () => {
@@ -46,14 +41,12 @@ describe('Tabs component', () => {
expect(vm.$el.querySelector('a').classList).toContain('active');
});
- it('updates active class on click', done => {
+ it('updates active class on click', () => {
vm.$el.querySelectorAll('a')[1].click();
- setTimeout(() => {
+ return vm.$nextTick(() => {
expect(vm.$el.querySelector('a').classList).not.toContain('active');
expect(vm.$el.querySelectorAll('a')[1].classList).toContain('active');
-
- done();
});
});
});
diff --git a/spec/javascripts/vue_shared/components/toggle_button_spec.js b/spec/frontend/vue_shared/components/toggle_button_spec.js
index ea0a89a3ab5..83bbb37a89a 100644
--- a/spec/javascripts/vue_shared/components/toggle_button_spec.js
+++ b/spec/frontend/vue_shared/components/toggle_button_spec.js
@@ -1,5 +1,5 @@
import Vue from 'vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import mountComponent from 'helpers/vue_mount_component_helper';
import toggleButton from '~/vue_shared/components/toggle_button.vue';
describe('Toggle Button', () => {
@@ -42,7 +42,7 @@ describe('Toggle Button', () => {
value: true,
});
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
it('renders is checked class', () => {
@@ -72,7 +72,7 @@ describe('Toggle Button', () => {
value: true,
disabledInput: true,
});
- spyOn(vm, '$emit');
+ jest.spyOn(vm, '$emit').mockImplementation(() => {});
});
it('renders disabled button', () => {
diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_svg_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_svg_spec.js
new file mode 100644
index 00000000000..ee6c2e2cc46
--- /dev/null
+++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_svg_spec.js
@@ -0,0 +1,27 @@
+import { shallowMount } from '@vue/test-utils';
+import UserAvatarSvg from '~/vue_shared/components/user_avatar/user_avatar_svg.vue';
+
+describe('User Avatar Svg Component', () => {
+ describe('Initialization', () => {
+ let wrapper;
+
+ beforeEach(() => {
+ wrapper = shallowMount(UserAvatarSvg, {
+ propsData: {
+ size: 99,
+ svg:
+ '<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path d="M1.707 15.707C1.077z"/></svg>',
+ },
+ });
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should have <svg> as a child element', () => {
+ expect(wrapper.element.tagName).toEqual('svg');
+ expect(wrapper.html()).toContain('<path');
+ });
+ });
+});
diff --git a/spec/javascripts/ide/stores/actions/tree_spec.js b/spec/javascripts/ide/stores/actions/tree_spec.js
index fabe44ce333..2201a3b4b57 100644
--- a/spec/javascripts/ide/stores/actions/tree_spec.js
+++ b/spec/javascripts/ide/stores/actions/tree_spec.js
@@ -30,6 +30,7 @@ describe('Multi-file store tree actions', () => {
store.state.currentBranchId = 'master';
store.state.projects.abcproject = {
web_url: '',
+ path_with_namespace: 'foo/abcproject',
};
});
@@ -57,7 +58,7 @@ describe('Multi-file store tree actions', () => {
store
.dispatch('getFiles', basicCallParameters)
.then(() => {
- expect(service.getFiles).toHaveBeenCalledWith('', '12345678');
+ expect(service.getFiles).toHaveBeenCalledWith('foo/abcproject', '12345678');
done();
})
diff --git a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js b/spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js
deleted file mode 100644
index 31644416439..00000000000
--- a/spec/javascripts/vue_shared/components/user_avatar/user_avatar_svg_spec.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import Vue from 'vue';
-import avatarSvg from 'icons/_icon_random.svg';
-import UserAvatarSvg from '~/vue_shared/components/user_avatar/user_avatar_svg.vue';
-
-const UserAvatarSvgComponent = Vue.extend(UserAvatarSvg);
-
-describe('User Avatar Svg Component', function() {
- describe('Initialization', function() {
- beforeEach(function() {
- this.propsData = {
- size: 99,
- svg: avatarSvg,
- };
-
- this.userAvatarSvg = new UserAvatarSvgComponent({
- propsData: this.propsData,
- }).$mount();
- });
-
- it('should return a defined Vue component', function() {
- expect(this.userAvatarSvg).toBeDefined();
- });
-
- it('should have <svg> as a child element', function() {
- expect(this.userAvatarSvg.$el.tagName).toEqual('svg');
- expect(this.userAvatarSvg.$el.innerHTML).toContain('<path');
- });
- });
-});
diff --git a/spec/lib/gitlab/git/attributes_parser_spec.rb b/spec/lib/gitlab/git/attributes_parser_spec.rb
index 94b7a086e59..45db4acd3ac 100644
--- a/spec/lib/gitlab/git/attributes_parser_spec.rb
+++ b/spec/lib/gitlab/git/attributes_parser_spec.rb
@@ -75,6 +75,14 @@ describe Gitlab::Git::AttributesParser, :seed_helper do
expect(subject.attributes('test.foo')).to eq({})
end
end
+
+ context 'when attributes data has binary data' do
+ let(:data) { "\xFF\xFE*\u0000.\u0000c\u0000s".b }
+
+ it 'returns an empty Hash' do
+ expect(subject.attributes('test.foo')).to eq({})
+ end
+ end
end
describe '#patterns' do
diff --git a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
index 85a5b1dacc7..11cf14523c2 100644
--- a/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
+++ b/spec/lib/gitlab/graphql/pagination/externally_paginated_array_connection_spec.rb
@@ -19,6 +19,20 @@ describe Gitlab::Graphql::Pagination::ExternallyPaginatedArrayConnection do
it_behaves_like 'connection with paged nodes' do
let(:paged_nodes_size) { values.size }
end
+
+ context 'when after or before is specified, they are ignored' do
+ # after and before are not used to filter the array, as they
+ # were already used to directly fetch the external array
+ it_behaves_like 'connection with paged nodes' do
+ let(:arguments) { { after: next_cursor } }
+ let(:paged_nodes_size) { values.size }
+ end
+
+ it_behaves_like 'connection with paged nodes' do
+ let(:arguments) { { before: prev_cursor } }
+ let(:paged_nodes_size) { values.size }
+ end
+ end
end
describe '#start_cursor' do
diff --git a/spec/models/design_management/design_spec.rb b/spec/models/design_management/design_spec.rb
index 555b7f04f86..95782c1f674 100644
--- a/spec/models/design_management/design_spec.rb
+++ b/spec/models/design_management/design_spec.rb
@@ -380,15 +380,8 @@ describe DesignManagement::Design do
end
end
- # TODO these tests are being temporarily skipped unless run in EE,
- # as we are in the process of moving Design Management to FOSS in 13.0
- # in steps. In the current step the routes have not yet been moved.
- #
- # See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283.
describe '#note_etag_key' do
it 'returns a correct etag key' do
- skip 'See https://gitlab.com/gitlab-org/gitlab/-/issues/212566#note_327724283' unless Gitlab.ee?
-
design = create(:design)
expect(design.note_etag_key).to eq(
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index cbbd0dae2eb..4fe612c1d0e 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -852,4 +852,20 @@ describe 'project routing' do
it_behaves_like 'redirecting a legacy project path', "/gitlab/gitlabhq/serverless", "/gitlab/gitlabhq/-/serverless"
end
end
+
+ describe Projects::DesignManagement::Designs::RawImagesController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/raw_image')).to route_to('projects/design_management/designs/raw_images#show', namespace_id: 'gitlab', project_id: 'gitlabhq', design_id: '1')
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/raw_image')).to route_to('projects/design_management/designs/raw_images#show', namespace_id: 'gitlab', project_id: 'gitlabhq', design_id: '1', sha: 'c6f00aa50b80887ada30a6fe517670be9f8f9ece')
+ end
+ end
+
+ describe Projects::DesignManagement::Designs::ResizedImageController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/resized_image/v432x230')).to route_to('projects/design_management/designs/resized_image#show', namespace_id: 'gitlab', project_id: 'gitlabhq', design_id: '1', id: 'v432x230')
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/v432x230')).to route_to('projects/design_management/designs/resized_image#show', namespace_id: 'gitlab', project_id: 'gitlabhq', design_id: '1', sha: 'c6f00aa50b80887ada30a6fe517670be9f8f9ece', id: 'v432x230')
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/invalid/resized_image/v432x230')).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/-/design_management/designs/1/invalid/resized_image/v432x230')
+ expect(get('/gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')).to route_to('application#route_not_found', unmatched_route: 'gitlab/gitlabhq/-/design_management/designs/1/c6f00aa50b80887ada30a6fe517670be9f8f9ece/resized_image/small')
+ end
+ end
end