Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-09-13 12:12:50 +00:00
parent bfd344aeac
commit 37a739daec
120 changed files with 951 additions and 665 deletions

View File

@ -261,6 +261,66 @@ ee:decomposition-multiple-db-parallel:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::All/
ee:object-storage:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
ee:object-storage-parallel:
extends: ee:object-storage
parallel: 2
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
ee:object-storage-aws:
extends: ee:object-storage
variables:
AWS_S3_ACCESS_KEY: $QA_AWS_S3_ACCESS_KEY
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
GITLAB_QA_OPTS: --omnibus-config object_storage_aws
ee:object-storage-aws-parallel:
extends: ee:object-storage-aws
parallel: 2
rules:
- !reference [ee:object-storage-parallel, rules]
ee:object-storage-gcs:
extends: ee:object-storage
variables:
GCS_BUCKET_NAME: $QA_GCS_BUCKET_NAME
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
ee:object-storage-gcs-parallel:
extends: ee:object-storage-gcs
parallel: 2
rules:
- !reference [ee:object-storage-parallel, rules]
ee:packages:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag packages
GITLAB_QA_OPTS: --omnibus-config packages
rules:
- !reference [.rules:test:qa-non-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::Packages/
ee:packages-parallel:
extends: ee:packages
parallel: 2
rules:
- !reference [.rules:test:qa-parallel, rules]
- if: $QA_SUITES =~ /Test::Instance::Packages/
# ------------------------------------------
# Non parallel jobs
# ------------------------------------------
@ -370,9 +430,6 @@ ee:registry:
ee:registry-with-cdn:
extends: .qa
before_script:
- unset GITLAB_QA_ADMIN_ACCESS_TOKEN
- !reference [.gitlab-qa-install, before_script]
variables:
QA_SCENARIO: Test::Integration::RegistryWithCDN
GCS_CDN_BUCKET_NAME: $QA_GCS_CDN_BUCKET_NAME
@ -380,6 +437,9 @@ ee:registry-with-cdn:
GOOGLE_CDN_JSON_KEY: $QA_GOOGLE_CDN_JSON_KEY
GOOGLE_CDN_SIGNURL_KEY: $QA_GOOGLE_CDN_SIGNURL_KEY
GOOGLE_CDN_SIGNURL_KEY_NAME: $QA_GOOGLE_CDN_SIGNURL_KEY_NAME
before_script:
- unset GITLAB_QA_ADMIN_ACCESS_TOKEN
- !reference [.qa, before_script]
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::RegistryWithCDN/
@ -434,55 +494,17 @@ ee:metrics:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Metrics/
ee:packages:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag packages
GITLAB_QA_OPTS: --omnibus-config packages
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::Packages/
ee:elasticsearch:
extends: .qa
variables:
QA_SCENARIO: "Test::Integration::Elasticsearch"
script:
before_script:
- unset ELASTIC_URL # unset url which is globally defined in .gitlab-ci.yml
- !reference [.qa, script]
- !reference [.qa, before_script]
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Integration::Elasticsearch/
ee:object-storage:
extends: .qa
variables:
QA_SCENARIO: Test::Instance::Image
QA_RSPEC_TAGS: --tag object_storage
GITLAB_QA_OPTS: --omnibus-config object_storage
rules:
- !reference [.rules:test:qa, rules]
- if: $QA_SUITES =~ /Test::Instance::ObjectStorage/
ee:object-storage-aws:
extends: ee:object-storage
variables:
AWS_S3_ACCESS_KEY: $QA_AWS_S3_ACCESS_KEY
AWS_S3_BUCKET_NAME: $QA_AWS_S3_BUCKET_NAME
AWS_S3_KEY_ID: $QA_AWS_S3_KEY_ID
AWS_S3_REGION: $QA_AWS_S3_REGION
GITLAB_QA_OPTS: --omnibus-config object_storage_aws
ee:object-storage-gcs:
extends: ee:object-storage
variables:
GCS_BUCKET_NAME: $QA_GCS_BUCKET_NAME
GOOGLE_PROJECT: $QA_GOOGLE_PROJECT
GOOGLE_JSON_KEY: $QA_GOOGLE_JSON_KEY
GOOGLE_CLIENT_EMAIL: $QA_GOOGLE_CLIENT_EMAIL
GITLAB_QA_OPTS: --omnibus-config object_storage_gcs
ee:registry-object-storage-tls:
extends: ee:object-storage-aws
variables:

View File

@ -4,26 +4,6 @@ Layout/FirstArrayElementIndentation:
Exclude:
- 'ee/lib/ee/api/helpers/award_emoji.rb'
- 'ee/spec/graphql/mutations/incident_management/escalation_policy/create_spec.rb'
- 'ee/spec/graphql/resolvers/dora_metrics_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/security_orchestration/scan_execution_policy_resolver_spec.rb'
- 'ee/spec/graphql/resolvers/timebox_report_resolver_spec.rb'
- 'ee/spec/graphql/types/ci/pipeline_type_spec.rb'
- 'ee/spec/graphql/types/dast_scanner_profile_type_spec.rb'
- 'ee/spec/graphql/types/dast_site_profile_type_spec.rb'
- 'ee/spec/helpers/paid_feature_callout_helper_spec.rb'
- 'ee/spec/helpers/trial_status_widget_helper_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/access_levels_spec.rb'
- 'ee/spec/lib/ee/gitlab/auth/ldap/sync/group_spec.rb'
- 'ee/spec/lib/ee/gitlab/usage_data_spec.rb'
- 'ee/spec/lib/gitlab/checks/diff_check_spec.rb'
- 'ee/spec/lib/gitlab/ci/config/security_orchestration_policies/processor_spec.rb'
- 'ee/spec/lib/gitlab/ci/templates/Jobs/browser_performance_testing_gitlab_ci_yaml_spec.rb'
- 'ee/spec/lib/gitlab/ci/templates/Jobs/dast_default_branch_gitlab_ci_yaml_spec.rb'
- 'ee/spec/lib/gitlab/ci/templates/Jobs/load_performance_testing_gitlab_ci_yaml_spec.rb'
- 'ee/spec/lib/gitlab/ci/yaml_processor_spec.rb'
- 'ee/spec/lib/gitlab/graphql/aggregations/epics/epic_node_spec.rb'
- 'ee/spec/lib/gitlab/graphql/aggregations/epics/lazy_links_aggregate_spec.rb'
- 'ee/spec/lib/gitlab/graphql/aggregations/issues/lazy_links_aggregate_spec.rb'
- 'ee/spec/lib/gitlab/graphql/loaders/bulk_epic_aggregate_loader_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_ci_builds_metric_spec.rb'
- 'ee/spec/lib/gitlab/usage/metrics/instrumentations/count_users_creating_ci_builds_metric_spec.rb'

View File

@ -428,6 +428,7 @@ RSpec/ExpectChange:
- 'spec/models/project_auto_devops_spec.rb'
- 'spec/models/project_import_state_spec.rb'
- 'spec/models/project_spec.rb'
- 'spec/models/project_statistics_spec.rb'
- 'spec/models/projects/build_artifacts_size_refresh_spec.rb'
- 'spec/models/projects/ci_feature_usage_spec.rb'
- 'spec/models/release_spec.rb'

View File

@ -340,6 +340,7 @@ RSpec/PredicateMatcher:
- 'spec/models/concerns/awardable_spec.rb'
- 'spec/models/concerns/chronic_duration_attribute_spec.rb'
- 'spec/models/concerns/ci/has_deployment_name_spec.rb'
- 'spec/models/concerns/counter_attribute_spec.rb'
- 'spec/models/concerns/featurable_spec.rb'
- 'spec/models/concerns/ignorable_columns_spec.rb'
- 'spec/models/concerns/integrations/has_data_fields_spec.rb'

View File

@ -23,11 +23,10 @@ class Projects::BlameController < Projects::ApplicationController
environment_params[:find_latest] = true
@environment = ::Environments::EnvironmentsByDeploymentsFinder.new(@project, current_user, environment_params).execute.last
blame_service = Projects::BlameService.new(@blob, @commit, params.permit(:page))
blame_service = Projects::BlameService.new(@blob, @commit, params.permit(:page, :no_pagination))
@blame = Gitlab::View::Presenter::Factory.new(blame_service.blame, project: @project, path: @path, page: blame_service.page).fabricate!
render locals: { blame_pagination: blame_service.pagination }
@blame_pagination = blame_service.pagination
end
end

View File

@ -64,20 +64,7 @@ class Projects::MergeRequests::DiffsController < Projects::MergeRequests::Applic
return unless stale?(etag: [cache_context + diff_options_hash.fetch(:paths, []), diffs])
if diff_options_hash[:paths].blank?
if Feature.enabled?(:remove_caching_diff_batches, @merge_request.project)
render json: PaginatedDiffSerializer.new(current_user: current_user).represent(diffs, options)
else
render_cached(
diffs,
with: PaginatedDiffSerializer.new(current_user: current_user),
cache_context: -> (_) { [Digest::SHA256.hexdigest(cache_context.to_s)] },
**options
)
end
else
render json: PaginatedDiffSerializer.new(current_user: current_user).represent(diffs, options)
end
end
# rubocop: enable Metrics/AbcSize

View File

@ -57,11 +57,13 @@ class Projects::MergeRequests::DraftsController < Projects::MergeRequests::Appli
end
if Gitlab::Utils.to_boolean(approve_params[:approve])
unless merge_request.approved_by?(current_user)
success = ::MergeRequests::ApprovalService.new(project: @project, current_user: current_user, params: approve_params).execute(merge_request)
unless success
return render json: { message: _('An error occurred while approving, please try again.') }, status: :internal_server_error
end
end
merge_request_activity_counter.track_submit_review_approve(user: current_user)
end

View File

@ -92,32 +92,6 @@ module BlobHelper
end
end
def replace_blob_link(project = @project, ref = @ref, path = @path, blob:)
modify_file_button(
project,
ref,
path,
blob: blob,
label: _("Replace"),
action: "replace",
btn_class: "default",
modal_type: "upload"
)
end
def delete_blob_link(project = @project, ref = @ref, path = @path, blob:)
modify_file_button(
project,
ref,
path,
blob: blob,
label: _("Delete"),
action: "delete",
btn_class: "default",
modal_type: "remove"
)
end
def can_modify_blob?(blob, project = @project, ref = @ref)
!blob.stored_externally? && can_edit_tree?(project, ref)
end

View File

@ -65,6 +65,10 @@ module CounterAttribute
def counter_attribute_after_flush(&callback)
after_flush_callbacks << callback
end
def counter_attribute_enabled?(attribute)
counter_attributes.include?(attribute)
end
end
# This method must only be called by FlushCounterIncrementsWorker
@ -157,7 +161,7 @@ module CounterAttribute
end
def counter_attribute_enabled?(attribute)
self.class.counter_attributes.include?(attribute)
self.class.counter_attribute_enabled?(attribute)
end
private

View File

@ -11,9 +11,10 @@ class ProjectStatistics < ApplicationRecord
default_value_for :snippets_size, 0
counter_attribute :build_artifacts_size
counter_attribute :storage_size
counter_attribute_after_flush do |project_statistic|
project_statistic.refresh_storage_size!
Namespaces::ScheduleAggregationWorker.perform_async(project_statistic.namespace_id)
end
@ -21,7 +22,6 @@ class ProjectStatistics < ApplicationRecord
COLUMNS_TO_REFRESH = [:repository_size, :wiki_size, :lfs_objects_size, :commit_count, :snippets_size, :uploads_size, :container_registry_size].freeze
INCREMENTABLE_COLUMNS = {
build_artifacts_size: %i[storage_size],
packages_size: %i[storage_size],
pipeline_artifacts_size: %i[storage_size],
snippets_size: %i[storage_size]
@ -109,21 +109,25 @@ class ProjectStatistics < ApplicationRecord
self.storage_size = storage_size
end
# Since this incremental update method does not call update_storage_size above,
# we have to update the storage_size here as additional column.
# Additional columns are updated depending on key => [columns], which allows
# to update statistics which are and also those which aren't included in storage_size
# or any other additional summary column in the future.
def refresh_storage_size!
update_storage_size
save!
end
# Since this incremental update method does not call update_storage_size above through before_save,
# we have to update the storage_size separately.
#
# For counter attributes, storage_size will be refreshed after the counter is flushed,
# through counter_attribute_after_flush
#
# For non-counter attributes, storage_size is updated depending on key => [columns] in INCREMENTABLE_COLUMNS
def self.increment_statistic(project, key, amount)
raise ArgumentError, "Cannot increment attribute: #{key}" unless INCREMENTABLE_COLUMNS.key?(key)
raise ArgumentError, "Cannot increment attribute: #{key}" unless incrementable_attribute?(key)
return if amount == 0
project.statistics.try do |project_statistics|
if project_statistics.counter_attribute_enabled?(key)
statistics_to_increment = [key] + INCREMENTABLE_COLUMNS[key].to_a
statistics_to_increment.each do |statistic|
project_statistics.delayed_increment_counter(statistic, amount)
end
if counter_attribute_enabled?(key)
project_statistics.delayed_increment_counter(key, amount)
else
legacy_increment_statistic(project, key, amount)
end
@ -149,6 +153,10 @@ class ProjectStatistics < ApplicationRecord
update_all(updates.join(', '))
end
def self.incrementable_attribute?(key)
INCREMENTABLE_COLUMNS.key?(key) || counter_attribute_enabled?(key)
end
private
def schedule_namespace_aggregation_worker

View File

@ -7,9 +7,24 @@ module Packages
overrides(:read_package)
rule { anonymous & ~project.public_project }.prevent_all
condition(:package_registry_access_level_feature_flag_enabled, scope: :subject) do
::Feature.enabled?(:package_registry_access_level, @subject)
end
rule { ~project.public_project & ~project.internal_access & ~project.project_allowed_for_job_token }.prevent_all
condition(:packages_enabled_for_everyone, scope: :subject) do
@subject.package_registry_access_level == ProjectFeature::PUBLIC
end
# This rule can be removed if the `package_registry_access_level` feature flag is removed.
# Reason: If the feature flag is globally enabled, this rule will never be executed.
rule { anonymous & ~project.public_project & ~package_registry_access_level_feature_flag_enabled }.prevent_all
# This rule can be removed if the `package_registry_access_level` feature flag is removed.
# Reason: If the feature flag is globally enabled, this rule will never be executed.
rule do
~project.public_project & ~project.internal_access &
~project.project_allowed_for_job_token & ~package_registry_access_level_feature_flag_enabled
end.prevent_all
rule { project.packages_disabled }.policy do
prevent(:read_package)
@ -30,6 +45,10 @@ module Packages
rule { project.write_package_registry_deploy_token }.policy do
enable :read_package
end
rule { package_registry_access_level_feature_flag_enabled & packages_enabled_for_everyone }.policy do
enable :read_package
end
end
end
end

View File

@ -10,6 +10,7 @@ module Projects
@blob = blob
@commit = commit
@page = extract_page(params)
@pagination_enabled = pagination_state(params)
end
attr_reader :page
@ -19,7 +20,7 @@ module Projects
end
def pagination
return unless pagination_enabled?
return unless pagination_enabled
Kaminari.paginate_array([], total_count: blob_lines_count, limit: per_page)
.tap { |pagination| pagination.max_paginates_per(per_page) }
@ -28,10 +29,10 @@ module Projects
private
attr_reader :blob, :commit
attr_reader :blob, :commit, :pagination_enabled
def blame_range
return unless pagination_enabled?
return unless pagination_enabled
first_line = (page - 1) * per_page + 1
last_line = (first_line + per_page).to_i - 1
@ -51,6 +52,12 @@ module Projects
PER_PAGE
end
def pagination_state(params)
return false if Gitlab::Utils.to_boolean(params[:no_pagination], default: false)
Feature.enabled?(:blame_page_pagination, commit.project)
end
def overlimit?(page)
page * per_page >= blob_lines_count + per_page
end
@ -58,9 +65,5 @@ module Projects
def blob_lines_count
@blob_lines_count ||= blob.data.lines.count
end
def pagination_enabled?
Feature.enabled?(:blame_page_pagination, commit.project)
end
end
end

View File

@ -158,14 +158,25 @@ module Projects
priority: UserProjectAccessChangedService::LOW_PRIORITY
)
else
@project.add_owner(@project.namespace.owner, current_user: current_user)
owner_user = @project.namespace.owner
owner_member = @project.add_owner(owner_user, current_user: current_user)
# There is a possibility that the sidekiq job to refresh the authorizations of the owner_user in this project
# isn't picked up (or finished) by the time the user is redirected to the newly created project's page.
# If that happens, the user will hit a 404. To avoid that scenario, we manually create a `project_authorizations` record for the user here.
if owner_member.persisted?
owner_user.project_authorizations.safe_find_or_create_by(
project: @project,
access_level: ProjectMember::OWNER
)
end
# During the process of adding a project owner, a check on permissions is made on the user which caches
# the max member access for that user on this project.
# Since that is `0` before the member is created - and we are still inside the request
# cycle when we need to do other operations that might check those permissions (e.g. write a commit)
# we need to purge that cache so that the updated permissions is fetched instead of using the outdated cached value of 0
# from before member creation
@project.team.purge_member_access_cache_for_user_id(@project.namespace.owner.id)
@project.team.purge_member_access_cache_for_user_id(owner_user.id)
end
end

View File

@ -1,9 +1,9 @@
- page_title _("Blame"), @blob.path, @ref
#blob-content-holder.tree-holder
#blob-content-holder.tree-holder{ data: { testid: 'blob-content-holder' } }
= render "projects/blob/breadcrumb", blob: @blob, blame: true
.file-holder
.file-holder.gl-overflow-hidden
= render "projects/blob/header", blob: @blob, blame: true
.file-blame-legend
@ -20,7 +20,7 @@
%span.legend-box.legend-box-9
%span.right-label Older
.table-responsive.file-content.blame.code{ class: user_color_scheme, data: { qa_selector: 'blame_file_content' } }
.table-responsive.file-content.blame.code{ class: "#{user_color_scheme} gl-rounded-0!", data: { qa_selector: 'blame_file_content' } }
%table
- current_line = @blame.first_line
- @blame.groups.each do |blame_group|
@ -59,5 +59,11 @@
- current_line += line_count
- if blame_pagination
= paginate(blame_pagination, theme: "gitlab")
- if @blame_pagination
.gl-display-flex.gl-justify-content-center.gl-flex-direction-column.gl-align-items-center.gl-p-3.gl-bg-gray-50.gl-border-t-solid.gl-border-t-1.gl-border-gray-100
= _('For faster browsing, not all history is shown.')
= render Pajamas::ButtonComponent.new(href: namespace_project_blame_path(namespace_id: @project.namespace, project_id: @project, id: @id, no_pagination: true), size: :small, button_options: { class: 'gl-mt-3' }) do |c|
= _('View entire blame')
- if @blame_pagination
= paginate(@blame_pagination, theme: "gitlab")

View File

@ -6,12 +6,6 @@
.file-actions.gl-display-flex.gl-align-items-center.gl-flex-wrap.gl-md-justify-content-end<
= render 'projects/blob/viewer_switcher', blob: blob unless blame
= render 'shared/web_ide_button', blob: blob
- unless blame
.btn-group{ role: "group", class: ("gl-ml-3" if current_user) }>
= render_if_exists 'projects/blob/header_file_locks_link'
- if current_user
= replace_blob_link(@project, @ref, @path, blob: blob)
= delete_blob_link(@project, @ref, @path, blob: blob)
.btn-group.gl-ml-3{ role: "group" }
= copy_blob_source_button(blob) unless blame
= open_raw_blob_button(blob)

View File

@ -11,6 +11,7 @@ class FlushCounterIncrementsWorker
data_consistency :always
sidekiq_options retry: 3
loggable_arguments 0, 2
# The increments in `ProjectStatistics` are owned by several teams depending
# on the counter

View File

@ -1,7 +1,7 @@
---
name: remove_caching_diff_batches
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95562
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/370933
name: draft_quick_action_non_toggle
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92654
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/368610
milestone: '15.4'
type: development
group: group::code review

View File

@ -0,0 +1,37 @@
const vm = require('vm');
/**
* This function uses Node's `vm` modules to evaluate the `module.exports` of a given source string
*
* Example:
*
* ```javascript
* const { exports: moduleExports } = evaluateModuleFromSource("const foo = 7;\n module.exports.bar = 10 + foo;");
*
* assert(moduleExports.bar === 17);
* ```
*
* @param {String} source to be evaluated using Node's `vm` modules
* @param {{ require: Function }} options used in the context during evaluation of the Node module
* @returns {{ exports: any }} exports added to the script's `module.exports` context
*/
const evaluateModuleFromSource = (source, { require } = {}) => {
const context = {
module: {
exports: {},
},
require,
};
try {
const script = new vm.Script(source);
script.runInNewContext(context);
} catch (e) {
console.error(e);
throw e;
}
return context.module;
};
module.exports = { evaluateModuleFromSource };

View File

@ -712,9 +712,6 @@ Gitlab.ee do
Settings.cron_jobs['ldap_sync_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['ldap_sync_worker']['cron'] ||= '30 1 * * *'
Settings.cron_jobs['ldap_sync_worker']['job_class'] = 'LdapSyncWorker'
Settings.cron_jobs['free_user_cap_data_remediation'] ||= Settingslogic.new({})
Settings.cron_jobs['free_user_cap_data_remediation']['cron'] ||= '17 6,10,14,18 * * *'
Settings.cron_jobs['free_user_cap_data_remediation']['job_class'] = 'Namespaces::FreeUserCap::RemediationWorker'
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker'] ||= Settingslogic.new({})
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['cron'] ||= '0 12 * * *'
Settings.cron_jobs['update_max_seats_used_for_gitlab_com_subscriptions_worker']['job_class'] = 'UpdateMaxSeatsUsedForGitlabComSubscriptionsWorker'

View File

@ -1,9 +1,10 @@
/* eslint-disable no-underscore-dangle */
const yaml = require('js-yaml');
const { evaluateModuleFromSource } = require('../helpers/evaluate_module_from_source');
const PLUGIN_NAME = 'GraphqlKnownOperationsPlugin';
const GRAPHQL_PATH_REGEX = /(query|mutation)\.graphql$/;
const OPERATION_NAME_SOURCE_REGEX = /^\s*module\.exports.*oneQuery.*"(\w+)"/gm;
/**
* Returns whether a given webpack module is a "graphql" module
@ -26,9 +27,19 @@ const getOperationNames = (module) => {
return [];
}
const matches = originalSource.source().toString().matchAll(OPERATION_NAME_SOURCE_REGEX);
const { exports: moduleExports } = evaluateModuleFromSource(originalSource.source().toString(), {
// what: stub require(...) when evaluating the graphql module
// why: require(...) is used to fetch fragments. We only need operation metadata, so it's fine to stub these out.
require: () => ({ definitions: [] }),
});
return Array.from(matches).map((match) => match[1]);
const names = moduleExports.definitions
.filter((x) => ['query', 'mutation'].includes(x.operation))
.map((x) => x.name?.value)
// why: It's possible for operations to not have a name. That violates our eslint rule, but either way, let's ignore those here.
.filter(Boolean);
return names;
};
const createFileContents = (knownOperations) => {
@ -60,7 +71,7 @@ const onSucceedModule = ({ module, knownOperations }) => {
return;
}
getOperationNames(module).forEach((x) => knownOperations.add(x));
getOperationNames(module).forEach((name) => knownOperations.add(name));
};
const onCompilerEmit = ({ compilation, knownOperations, filename }) => {

View File

@ -0,0 +1,11 @@
# frozen_string_literal: true
class RemoveFreeUserCapRemediationWorker < Gitlab::Database::Migration[2.0]
def up
Sidekiq::Cron::Job.find('free_user_cap_data_remediation')&.destroy
end
def down
# no-op
end
end

View File

@ -0,0 +1,16 @@
# frozen_string_literal: true
class AddIndexToTodosPendingQuery < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'index_on_todos_user_project_target_and_state'
COLUMNS = %i[user_id project_id target_type target_id id].freeze
def up
add_concurrent_index :todos, COLUMNS, name: INDEX_NAME, where: "state = 'pending'"
end
def down
remove_concurrent_index_by_name :todos, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
d5d264f90203ba371edcf0688d1227aa69cbf0018033d141257e4c88072ee7d7

View File

@ -0,0 +1 @@
30d9f3352daa48f529486030e30667a1339b04e96b207be815505477ab498adb

View File

@ -29531,6 +29531,8 @@ CREATE INDEX index_on_projects_path ON projects USING btree (path);
CREATE INDEX index_on_routes_lower_path ON routes USING btree (lower((path)::text));
CREATE INDEX index_on_todos_user_project_target_and_state ON todos USING btree (user_id, project_id, target_type, target_id, id) WHERE ((state)::text = 'pending'::text);
CREATE INDEX index_on_users_lower_email ON users USING btree (lower((email)::text));
CREATE INDEX index_on_users_lower_username ON users USING btree (lower((username)::text));

View File

@ -329,7 +329,6 @@ GET /groups/:id/billable_members
| `id` | integer/string | yes | The ID or [URL-encoded path of the group](index.md#namespaced-path-encoding) owned by the authenticated user |
| `search` | string | no | A query string to search for group members by name, username, or public email. |
| `sort` | string | no | A query string containing parameters that specify the sort attribute and order. See supported values below. |
| `include_awaiting_members` | boolean | no | Determines if awaiting members are included. |
The supported values for the `sort` attribute are:

View File

@ -20,7 +20,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
| `id` | integer/string| yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) owned by the authenticated user|
| `order_by` | string | no | Return tags ordered by `name` or `updated` fields. Default is `updated` |
| `order_by` | string | no | Return tags ordered by `name`, `updated`, or `version` (since 15.4) fields. Default is `updated` |
| `sort` | string | no | Return tags sorted in `asc` or `desc` order. Default is `desc` |
| `search` | string | no | Return list of tags matching the search criteria. You can use `^term` and `term$` to find tags that begin and end with `term` respectively. No other regular expressions are supported. |

View File

@ -67,7 +67,7 @@ as it can cause the pipeline to behave unexpectedly.
| `CI_JOB_JWT` | 12.10 | all | A RS256 JSON web token to authenticate with third party systems that support JWT authentication, for example [HashiCorp's Vault](../secrets/index.md). |
| `CI_JOB_JWT_V1` | 14.6 | all | The same value as `CI_JOB_JWT`. |
| `CI_JOB_JWT_V2` | 14.6 | all | [**alpha:**](../../policy/alpha-beta-support.md#alpha-features) A newly formatted RS256 JSON web token to increase compatibility. Similar to `CI_JOB_JWT`, except the issuer (`iss`) claim is changed from `gitlab.com` to `https://gitlab.com`, `sub` has changed from `job_id` to a string that contains the project path, and an `aud` claim is added. Format is subject to change. Be aware, the `aud` field is a constant value. Trusting JWTs in multiple relying parties can lead to [one RP sending a JWT to another one and acting maliciously as a job](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/72555#note_769112331). |
| `CI_JOB_MANUAL` | 8.12 | all | `true` if a job was started manually. |
| `CI_JOB_MANUAL` | 8.12 | all | Only available if the job was started manually. `true` when available. |
| `CI_JOB_NAME` | 9.0 | 0.5 | The name of the job. |
| `CI_JOB_STAGE` | 9.0 | 0.5 | The name of the job's stage. |
| `CI_JOB_STATUS` | all | 13.5 | The status of the job as each runner stage is executed. Use with [`after_script`](../yaml/index.md#after_script). Can be `success`, `failed`, or `canceled`. |

View File

@ -66,7 +66,7 @@ The generated service account has the following roles:
- `roles/cloudbuild.builds.builder`
- `roles/run.admin`
- `roles/storage.admin`
- `roles/cloudsql.admin`
- `roles/cloudsql.client`
- `roles/browser`
You can enhance security by storing CI variables in secret managers. Learn more about [secret management with GitLab](../ci/secrets/index.md).

View File

@ -20,6 +20,17 @@ See also [guidelines for reusing abstractions](../reusing_abstractions.md).
Everything listed here can be [reopened for discussion](https://about.gitlab.com/handbook/values/#disagree-commit-and-disagree).
## String literals quoting
Due to the sheer amount of work to rectify, we do not care whether string
literals are single, or double quoted.
Previous discussions include:
- <https://gitlab.com/gitlab-org/gitlab-foss/-/issues/44234>
- <https://gitlab.com/gitlab-org/gitlab-foss/-/issues/36076>
- <https://gitlab.com/gitlab-org/gitlab/-/issues/198046>
## Instance variable access using `attr_reader`
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/52351) in GitLab 14.1.

View File

@ -20,6 +20,7 @@ the **Merge** button until you remove the **Draft** flag:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32692) in GitLab 13.2, Work-In-Progress (WIP) merge requests were renamed to **Draft**.
> - [Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/228685) all support for using **WIP** in GitLab 14.8.
> - **Mark as draft** and **Mark as ready** buttons [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/227421) in GitLab 13.5.
> `/draft` quick action as a toggle [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92654) in GitLab 15.3.
There are several ways to flag a merge request as a draft:
@ -29,8 +30,7 @@ There are several ways to flag a merge request as a draft:
below the **Title** field.
- **Commenting in an existing merge request**: Add the `/draft`
[quick action](../quick_actions.md#issues-merge-requests-and-epics)
in a comment. This quick action is a toggle, and can be repeated to change the status
back to Ready.
in a comment. GitLab 15.4 [deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92654) the toggle behavior of `/draft`. To mark a merge request as ready, use `/ready`.
- **Creating a commit**: Add `draft:`, `Draft:`, `fixup!`, or `Fixup!` to the
beginning of a commit message targeting the merge request's source branch. This
is not a toggle, and adding this text again in a later commit doesn't mark the

View File

@ -67,7 +67,7 @@ threads. Some quick actions might not be available to all subscription tiers.
| `/copy_metadata <#issue>` | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No | Copy labels and milestone from another issue in the project. |
| `/create_merge_request <branch name>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Create a new merge request starting from the current issue. |
| `/done` | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes | Mark to do as done. |
| `/draft` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Toggle the [draft status](merge_requests/drafts.md). |
| `/draft` | **{dotted-circle}** No | **{check-circle}** Yes | **{dotted-circle}** No | Set the [draft status](merge_requests/drafts.md). Use for toggling the draft status ([deprecated](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/92654) in GitLab 15.4.) |
| `/due <date>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Set due date. Examples of valid `<date>` include `in 2 days`, `this Friday` and `December 31st`. |
| `/duplicate <#issue>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Close this issue and mark as a duplicate of another issue. Also, mark both as related. |
| `/epic <epic>` | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No | Add to epic `<epic>`. The `<epic>` value should be in the format of `&epic`, `group&epic`, or a URL to an epic. |

View File

@ -22,8 +22,8 @@ module API
params do
optional :sort, type: String, values: %w[asc desc], default: 'desc',
desc: 'Return tags sorted in updated by `asc` or `desc` order.'
optional :order_by, type: String, values: %w[name updated], default: 'updated',
desc: 'Return tags ordered by `name` or `updated` fields.'
optional :order_by, type: String, values: %w[name updated version], default: 'updated',
desc: 'Return tags ordered by `name`, `updated`, `version` fields.'
optional :search, type: String, desc: 'Return list of tags matching the search criteria'
optional :page_token, type: String, desc: 'Name of tag to start the paginaition from'
use :pagination

View File

@ -88,33 +88,21 @@ module Gitlab
@execution_message[:rebase] = _('Scheduled a rebase of branch %{branch}.') % { branch: branch }
end
desc { _('Toggle the Draft status') }
desc { _('Set the Draft status') }
explanation do
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
if quick_action_target.draft?
_("Marks this %{noun} as ready.")
else
_("Marks this %{noun} as a draft.")
end % { noun: noun }
draft_action_message(_("Marks"))
end
execution_message do
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
if quick_action_target.draft?
_("Marked this %{noun} as ready.")
else
_("Marked this %{noun} as a draft.")
end % { noun: noun }
draft_action_message(_("Marked"))
end
types MergeRequest
condition do
quick_action_target.respond_to?(:draft?) &&
# Allow it to mark as draft on MR creation page or through MR notes
#
(quick_action_target.new_record? || current_user.can?(:"update_#{quick_action_target.to_ability_name}", quick_action_target))
end
command :draft do
@updates[:wip_event] = quick_action_target.draft? ? 'ready' : 'draft'
@updates[:wip_event] = draft_action_command
end
desc { _('Set the Ready status') }
@ -317,6 +305,25 @@ module Gitlab
end
end
def draft_action_message(verb)
noun = quick_action_target.to_ability_name.humanize(capitalize: false)
if !quick_action_target.draft?
_("%{verb} this %{noun} as a draft.")
elsif Feature.disabled?(:draft_quick_action_non_toggle, quick_action_target.project)
_("%{verb} this %{noun} as ready.")
else
_("No change to this %{noun}'s draft status.")
end % { verb: verb, noun: noun }
end
def draft_action_command
if Feature.disabled?(:draft_quick_action_non_toggle, quick_action_target.project)
quick_action_target.draft? ? 'ready' : 'draft'
else
'draft'
end
end
def merge_orchestration_service
@merge_orchestration_service ||= ::MergeRequests::MergeOrchestrationService.new(project, current_user)
end

View File

@ -22,7 +22,7 @@ module GoogleApi
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/monitoring"
].freeze
ROLES_LIST = %w[roles/iam.serviceAccountUser roles/artifactregistry.admin roles/cloudbuild.builds.builder roles/run.admin roles/storage.admin roles/cloudsql.admin roles/browser].freeze
ROLES_LIST = %w[roles/iam.serviceAccountUser roles/artifactregistry.admin roles/cloudbuild.builds.builder roles/run.admin roles/storage.admin roles/cloudsql.client roles/browser].freeze
REVOKE_URL = 'https://oauth2.googleapis.com/revoke'
class << self

View File

@ -1180,6 +1180,12 @@ msgstr ""
msgid "%{verb} %{time_spent_value} spent time."
msgstr ""
msgid "%{verb} this %{noun} as a draft."
msgstr ""
msgid "%{verb} this %{noun} as ready."
msgstr ""
msgid "%{webhooks_link_start}%{webhook_type}%{link_end} enable you to send notifications to web applications in response to events in a group or project."
msgstr ""
@ -16781,6 +16787,9 @@ msgstr ""
msgid "For example, the application using the token or the purpose of the token. Do not give sensitive information for the name of the token, as it will be visible to all %{resource_type} members."
msgstr ""
msgid "For faster browsing, not all history is shown."
msgstr ""
msgid "For files larger than this limit, only index the file name. The file content is neither indexed nor searchable."
msgstr ""
@ -24281,6 +24290,9 @@ msgstr ""
msgid "MarkdownToolbar|Supports %{markdownDocsLinkStart}Markdown%{markdownDocsLinkEnd}"
msgstr ""
msgid "Marked"
msgstr ""
msgid "Marked For Deletion At - %{deletion_time}"
msgstr ""
@ -24290,9 +24302,6 @@ msgstr ""
msgid "Marked as ready. Merging is now allowed."
msgstr ""
msgid "Marked this %{noun} as a draft."
msgstr ""
msgid "Marked this %{noun} as ready."
msgstr ""
@ -24305,7 +24314,7 @@ msgstr ""
msgid "Marked to do as done."
msgstr ""
msgid "Marks this %{noun} as a draft."
msgid "Marks"
msgstr ""
msgid "Marks this %{noun} as ready."
@ -36334,6 +36343,9 @@ msgstr ""
msgid "Set target branch to %{branch_name}."
msgstr ""
msgid "Set the Draft status"
msgstr ""
msgid "Set the Ready status"
msgstr ""
@ -41420,9 +41432,6 @@ msgstr ""
msgid "Toggle sidebar"
msgstr ""
msgid "Toggle the Draft status"
msgstr ""
msgid "Toggle the Performance Bar"
msgstr ""
@ -43485,6 +43494,9 @@ msgstr ""
msgid "View eligible approvers"
msgstr ""
msgid "View entire blame"
msgstr ""
msgid "View epics list"
msgstr ""
@ -46047,6 +46059,9 @@ msgstr ""
msgid "added %{emails}"
msgstr ""
msgid "added a %{link_type} link"
msgstr ""
msgid "added a Zoom call to this issue"
msgstr ""
@ -47701,6 +47716,9 @@ msgstr ""
msgid "removed"
msgstr ""
msgid "removed a %{link_type} link"
msgstr ""
msgid "removed a Zoom call from this issue"
msgstr ""

View File

@ -14,11 +14,6 @@ module QA
element :lock_button
end
view 'app/helpers/blob_helper.rb' do
element :edit_button, "_('Edit')" # rubocop:disable QA/ElementWithPattern
element :delete_button, '_("Delete")' # rubocop:disable QA/ElementWithPattern
end
view 'app/assets/javascripts/vue_shared/components/web_ide_link.vue' do
element :edit_button
end

View File

@ -471,6 +471,21 @@ RSpec.describe Projects::MergeRequests::DraftsController do
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to have_received(:track_submit_review_approve).with(user: user)
end
context 'when merge request is already approved by user' do
before do
create(:approval, merge_request: merge_request, user: user)
end
it 'does return 200' do
post :publish, params: params.merge!(approve: true)
expect(response).to have_gitlab_http_status(:ok)
expect(Gitlab::UsageDataCounters::MergeRequestActivityUniqueCounter)
.to have_received(:track_submit_review_approve).with(user: user)
end
end
end
end
end

View File

@ -35,9 +35,11 @@ RSpec.describe 'File blame', :js do
it 'displays the blame page without pagination' do
visit_blob_blame(path)
within '[data-testid="blob-content-holder"]' do
expect(page).to have_css('.blame-commit')
expect(page).not_to have_css('.gl-pagination')
end
end
context 'when blob length is over the blame range limit' do
before do
@ -47,13 +49,16 @@ RSpec.describe 'File blame', :js do
it 'displays two first lines of the file with pagination' do
visit_blob_blame(path)
within '[data-testid="blob-content-holder"]' do
expect(page).to have_css('.blame-commit')
expect(page).to have_css('.gl-pagination')
expect(page).to have_link _('View entire blame')
expect(page).to have_css('#L1')
expect(page).not_to have_css('#L3')
expect(find('.page-link.active')).to have_text('1')
end
end
context 'when user clicks on the next button' do
before do
@ -63,17 +68,37 @@ RSpec.describe 'File blame', :js do
end
it 'displays next two lines of the file with pagination' do
within '[data-testid="blob-content-holder"]' do
expect(page).not_to have_css('#L1')
expect(page).to have_css('#L3')
expect(find('.page-link.active')).to have_text('2')
end
end
it 'correctly redirects to the prior blame page' do
within '[data-testid="blob-content-holder"]' do
find('.version-link').click
expect(find('.page-link.active')).to have_text('2')
end
end
end
context 'when user clicks on View entire blame button' do
before do
visit_blob_blame(path)
end
it 'displays the blame page without pagination' do
within '[data-testid="blob-content-holder"]' do
click_link _('View entire blame')
expect(page).to have_css('#L1')
expect(page).to have_css('#L3')
expect(page).not_to have_css('.gl-pagination')
end
end
end
context 'when feature flag disabled' do
before do
@ -83,8 +108,11 @@ RSpec.describe 'File blame', :js do
it 'displays the blame page without pagination' do
visit_blob_blame(path)
within '[data-testid="blob-content-holder"]' do
expect(page).to have_css('.blame-commit')
expect(page).not_to have_css('.gl-pagination')
expect(page).not_to have_link _('View entire blame')
end
end
end
end
@ -99,6 +127,7 @@ RSpec.describe 'File blame', :js do
it 'displays two hundred lines of the file with pagination' do
visit_blob_blame(path)
within '[data-testid="blob-content-holder"]' do
expect(page).to have_css('.blame-commit')
expect(page).to have_css('.gl-pagination')
@ -106,19 +135,22 @@ RSpec.describe 'File blame', :js do
expect(page).not_to have_css('#L201')
expect(find('.page-link.active')).to have_text('1')
end
end
context 'when user clicks on the next button' do
before do
visit_blob_blame(path)
find('.js-next-button').click
end
it 'displays next two hundred lines of the file with pagination' do
within '[data-testid="blob-content-holder"]' do
find('.js-next-button').click
expect(page).not_to have_css('#L1')
expect(page).to have_css('#L201')
expect(find('.page-link.active')).to have_text('2')
end
end
end
end
end

View File

@ -17,16 +17,16 @@ describe('keepAlive', () => {
});
it('converts a component to a keep-alive component', async () => {
const { element } = wrapper.find(component);
const { element } = wrapper.findComponent(component);
await wrapper.vm.deactivate();
expect(wrapper.find(component).exists()).toBe(false);
expect(wrapper.findComponent(component).exists()).toBe(false);
await wrapper.vm.activate();
// assert that when the component is destroyed and re-rendered, the
// newly rendered component has the reference to the old component
// (i.e. the old component was deactivated and activated)
expect(wrapper.find(component).element).toBe(element);
expect(wrapper.findComponent(component).element).toBe(element);
});
});

View File

@ -23,12 +23,14 @@ describe('~/access_tokens/components/new_access_token_app', () => {
};
const triggerSuccess = async (newToken = 'new token') => {
wrapper.find(DomElementListener).vm.$emit(EVENT_SUCCESS, { detail: [{ new_token: newToken }] });
wrapper
.findComponent(DomElementListener)
.vm.$emit(EVENT_SUCCESS, { detail: [{ new_token: newToken }] });
await nextTick();
};
const triggerError = async (errors = ['1', '2']) => {
wrapper.find(DomElementListener).vm.$emit(EVENT_ERROR, { detail: [{ errors }] });
wrapper.findComponent(DomElementListener).vm.$emit(EVENT_ERROR, { detail: [{ errors }] });
await nextTick();
};

View File

@ -48,8 +48,8 @@ describe('AddContextCommitsModal', () => {
return wrapper;
};
const findModal = () => wrapper.find(GlModal);
const findSearch = () => wrapper.find(GlSearchBoxByType);
const findModal = () => wrapper.findComponent(GlModal);
const findSearch = () => wrapper.findComponent(GlSearchBoxByType);
beforeEach(() => {
wrapper = createWrapper();
@ -107,7 +107,7 @@ describe('AddContextCommitsModal', () => {
it('a disabled ok button in first tab, when row is selected in second tab', () => {
createWrapper({ selectedContextCommits: [commit] });
expect(wrapper.find(GlModal).attributes('ok-disabled')).toBe('true');
expect(wrapper.findComponent(GlModal).attributes('ok-disabled')).toBe('true');
});
});

View File

@ -32,7 +32,7 @@ describe('ReviewTabContainer', () => {
it('shows loading icon when commits are being loaded', () => {
createWrapper({ isLoading: true });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('shows loading error text when API call fails', () => {

View File

@ -77,8 +77,8 @@ describe('TopicSelect', () => {
const dropdownItems = findAllDropdownItems();
expect(dropdownItems.at(0).find(GlAvatarLabeled).props('label')).toBe('Topic 1');
expect(dropdownItems.at(1).find(GlAvatarLabeled).props('label')).toBe('GitLab');
expect(dropdownItems.at(0).findComponent(GlAvatarLabeled).props('label')).toBe('Topic 1');
expect(dropdownItems.at(1).findComponent(GlAvatarLabeled).props('label')).toBe('GitLab');
});
it('emits `click` event when topic selected', () => {

View File

@ -25,7 +25,7 @@ describe('AlertManagementEmptyState', () => {
}
});
const EmptyState = () => wrapper.find(GlEmptyState);
const EmptyState = () => wrapper.findComponent(GlEmptyState);
describe('Empty state', () => {
it('shows empty state', () => {

View File

@ -28,8 +28,8 @@ describe('AlertManagementList', () => {
describe('Alert List Wrapper', () => {
it('should show the empty state when alerts are not enabled', () => {
expect(wrapper.find(AlertManagementEmptyState).exists()).toBe(true);
expect(wrapper.find(AlertManagementTable).exists()).toBe(false);
expect(wrapper.findComponent(AlertManagementEmptyState).exists()).toBe(true);
expect(wrapper.findComponent(AlertManagementTable).exists()).toBe(false);
});
it('should show the alerts table when alerts are enabled', () => {
@ -39,8 +39,8 @@ describe('AlertManagementList', () => {
},
});
expect(wrapper.find(AlertManagementEmptyState).exists()).toBe(false);
expect(wrapper.find(AlertManagementTable).exists()).toBe(true);
expect(wrapper.findComponent(AlertManagementEmptyState).exists()).toBe(false);
expect(wrapper.findComponent(AlertManagementTable).exists()).toBe(true);
});
});
});

View File

@ -172,8 +172,8 @@ describe('AlertManagementTable', () => {
await nextTick();
expect(wrapper.find(GlTable).exists()).toBe(true);
expect(findAlertsTable().find(GlIcon).classes('icon-critical')).toBe(true);
expect(wrapper.findComponent(GlTable).exists()).toBe(true);
expect(findAlertsTable().findComponent(GlIcon).classes('icon-critical')).toBe(true);
});
it('renders severity text', () => {
@ -200,7 +200,7 @@ describe('AlertManagementTable', () => {
loading: false,
});
const avatar = findAssignees().at(1).find(GlAvatar);
const avatar = findAssignees().at(1).findComponent(GlAvatar);
const { src, label } = avatar.attributes();
const { name, avatarUrl } = mockAlerts[1].assignees.nodes[0];

View File

@ -47,7 +47,7 @@ describe('AlertMappingBuilder', () => {
expect(findColumnInRow(0, 2).text()).toContain(i18n.columns.payloadKeyTitle);
expect(findColumnInRow(0, 3).text()).toContain(i18n.columns.fallbackKeyTitle);
const fallbackColumnIcon = findColumnInRow(0, 3).find(GlIcon);
const fallbackColumnIcon = findColumnInRow(0, 3).findComponent(GlIcon);
expect(fallbackColumnIcon.exists()).toBe(true);
expect(fallbackColumnIcon.attributes('name')).toBe('question');
expect(fallbackColumnIcon.attributes('title')).toBe(i18n.fallbackTooltip);
@ -55,7 +55,7 @@ describe('AlertMappingBuilder', () => {
it('renders disabled form input for each mapped field', () => {
alertFields.forEach((field, index) => {
const input = findColumnInRow(index + 1, 0).find(GlFormInput);
const input = findColumnInRow(index + 1, 0).findComponent(GlFormInput);
const types = field.types.map((t) => capitalizeFirstCharacter(t.toLowerCase())).join(' or ');
expect(input.attributes('value')).toBe(`${field.label} (${types})`);
expect(input.attributes('disabled')).toBe('');
@ -71,7 +71,7 @@ describe('AlertMappingBuilder', () => {
it('renders mapping dropdown for each field', () => {
alertFields.forEach(({ types }, index) => {
const dropdown = findColumnInRow(index + 1, 2).find(GlDropdown);
const dropdown = findColumnInRow(index + 1, 2).findComponent(GlDropdown);
const { searchBox, dropdownItems, mappingOptions } = getDropdownContent(dropdown, types);
expect(dropdown.exists()).toBe(true);
@ -82,7 +82,7 @@ describe('AlertMappingBuilder', () => {
it('renders fallback dropdown only for the fields that have fallback', () => {
alertFields.forEach(({ types, numberOfFallbacks }, index) => {
const dropdown = findColumnInRow(index + 1, 3).find(GlDropdown);
const dropdown = findColumnInRow(index + 1, 3).findComponent(GlDropdown);
expect(dropdown.exists()).toBe(Boolean(numberOfFallbacks));
if (numberOfFallbacks) {
@ -96,8 +96,8 @@ describe('AlertMappingBuilder', () => {
it('emits event with selected mapping', () => {
const mappingToSave = { fieldName: 'TITLE', mapping: 'PARSED_TITLE' };
jest.spyOn(transformationUtils, 'transformForSave').mockReturnValue(mappingToSave);
const dropdown = findColumnInRow(1, 2).find(GlDropdown);
const option = dropdown.find(GlDropdownItem);
const dropdown = findColumnInRow(1, 2).findComponent(GlDropdown);
const option = dropdown.findComponent(GlDropdownItem);
option.vm.$emit('click');
expect(wrapper.emitted('onMappingUpdate')[0]).toEqual([mappingToSave]);
});

View File

@ -5,7 +5,7 @@ describe('Alert integration settings form', () => {
let wrapper;
const service = { updateSettings: jest.fn().mockResolvedValue() };
const findForm = () => wrapper.find({ ref: 'settingsForm' });
const findForm = () => wrapper.findComponent({ ref: 'settingsForm' });
beforeEach(() => {
wrapper = shallowMount(AlertsSettingsForm, {

View File

@ -53,8 +53,8 @@ describe('AlertIntegrationsList', () => {
mountComponent();
});
const findTableComponent = () => wrapper.find(GlTable);
const findTableComponentRows = () => wrapper.find(GlTable).findAll('table tbody tr');
const findTableComponent = () => wrapper.findComponent(GlTable);
const findTableComponentRows = () => wrapper.findComponent(GlTable).findAll('table tbody tr');
const finsStatusCell = () => wrapper.findAll('[data-testid="integration-activated-status"]');
it('renders a table', () => {
@ -78,7 +78,7 @@ describe('AlertIntegrationsList', () => {
describe('integration status', () => {
it('enabled', () => {
const cell = finsStatusCell().at(0);
const activatedIcon = cell.find(GlIcon);
const activatedIcon = cell.findComponent(GlIcon);
expect(cell.text()).toBe(i18n.status.enabled.name);
expect(activatedIcon.attributes('name')).toBe('check');
expect(activatedIcon.attributes('title')).toBe(i18n.status.enabled.tooltip);
@ -86,7 +86,7 @@ describe('AlertIntegrationsList', () => {
it('disabled', () => {
const cell = finsStatusCell().at(1);
const notActivatedIcon = cell.find(GlIcon);
const notActivatedIcon = cell.findComponent(GlIcon);
expect(cell.text()).toBe(i18n.status.disabled.name);
expect(notActivatedIcon.attributes('name')).toBe('warning-solid');
expect(notActivatedIcon.attributes('title')).toBe(i18n.status.disabled.tooltip);

View File

@ -325,9 +325,9 @@ describe('AlertsSettingsForm', () => {
});
await nextTick();
expect(findSamplePayloadSection().find(GlFormTextarea).attributes('disabled')).toBe(
disabled,
);
expect(
findSamplePayloadSection().findComponent(GlFormTextarea).attributes('disabled'),
).toBe(disabled);
});
});

View File

@ -63,14 +63,14 @@ describe('AlertsSettingsWrapper', () => {
const findLoader = () => wrapper.findComponent(IntegrationsList).findComponent(GlLoadingIcon);
const findIntegrationsList = () => wrapper.findComponent(IntegrationsList);
const findIntegrations = () => wrapper.find(IntegrationsList).findAll('table tbody tr');
const findIntegrations = () => wrapper.findComponent(IntegrationsList).findAll('table tbody tr');
const findAddIntegrationBtn = () => wrapper.findByTestId('add-integration-btn');
const findAlertsSettingsForm = () => wrapper.findComponent(AlertsSettingsForm);
const findAlert = () => wrapper.findComponent(GlAlert);
function destroyHttpIntegration(localWrapper) {
localWrapper
.find(IntegrationsList)
.findComponent(IntegrationsList)
.vm.$emit('delete-integration', { id: integrationToDestroy.id });
}
@ -189,7 +189,7 @@ describe('AlertsSettingsWrapper', () => {
data: { integrations: [] },
loading: true,
});
expect(wrapper.find(IntegrationsList).exists()).toBe(true);
expect(wrapper.findComponent(IntegrationsList).exists()).toBe(true);
expect(findLoader().exists()).toBe(true);
});
});

View File

@ -18,7 +18,7 @@ describe('Activity Chart Bundle', () => {
wrapper = null;
});
const findChart = () => wrapper.find(GlColumnChart);
const findChart = () => wrapper.findComponent(GlColumnChart);
const findNoData = () => wrapper.find('[data-testid="noActivityChartData"]');
describe('Activity Chart', () => {

View File

@ -30,7 +30,7 @@ describe('MetricPopover', () => {
const findAllMetricLinks = () => wrapper.findAll('[data-testid="metric-link"]');
const findMetricDescription = () => wrapper.findByTestId('metric-description');
const findMetricDocsLink = () => wrapper.findByTestId('metric-docs-link');
const findMetricDocsLinkIcon = () => findMetricDocsLink().find(GlIcon);
const findMetricDocsLinkIcon = () => findMetricDocsLink().findComponent(GlIcon);
afterEach(() => {
wrapper.destroy();
@ -83,7 +83,9 @@ describe('MetricPopover', () => {
const allLinkContainers = findAllMetricLinks();
expect(allLinkContainers.at(idx).text()).toContain(link.name);
expect(allLinkContainers.at(idx).find(GlLink).attributes('href')).toBe(link.url);
expect(allLinkContainers.at(idx).findComponent(GlLink).attributes('href')).toBe(
link.url,
);
});
});

View File

@ -79,7 +79,7 @@ describe('ProjectsDropdownFilter component', () => {
const findClearAllButton = () => wrapper.findByText('Clear all');
const findSelectedProjectsLabel = () => wrapper.findComponent(GlTruncate);
const findDropdown = () => wrapper.find(GlDropdown);
const findDropdown = () => wrapper.findComponent(GlDropdown);
const findDropdownItems = () =>
findDropdown()

View File

@ -21,7 +21,7 @@ describe('UsageTrendsApp', () => {
});
it('displays the usage counts component', () => {
expect(wrapper.find(UsageCounts).exists()).toBe(true);
expect(wrapper.findComponent(UsageCounts).exists()).toBe(true);
});
['Total projects & groups', 'Pipelines', 'Issues & merge requests'].forEach((usage) => {
@ -35,6 +35,6 @@ describe('UsageTrendsApp', () => {
});
it('displays the users chart component', () => {
expect(wrapper.find(UsersChart).exists()).toBe(true);
expect(wrapper.findComponent(UsersChart).exists()).toBe(true);
});
});

View File

@ -50,9 +50,9 @@ describe('UsageTrendsCountChart', () => {
wrapper = null;
});
const findLoader = () => wrapper.find(ChartSkeletonLoader);
const findChart = () => wrapper.find(GlLineChart);
const findAlert = () => wrapper.find(GlAlert);
const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
const findChart = () => wrapper.findComponent(GlLineChart);
const findAlert = () => wrapper.findComponent(GlAlert);
describe('while loading', () => {
beforeEach(() => {

View File

@ -47,9 +47,9 @@ describe('UsersChart', () => {
wrapper = null;
});
const findLoader = () => wrapper.find(ChartSkeletonLoader);
const findAlert = () => wrapper.find(GlAlert);
const findChart = () => wrapper.find(GlAreaChart);
const findLoader = () => wrapper.findComponent(ChartSkeletonLoader);
const findAlert = () => wrapper.findComponent(GlAlert);
const findChart = () => wrapper.findComponent(GlAreaChart);
describe('while loading', () => {
beforeEach(() => {

View File

@ -39,8 +39,8 @@ describe('Keep latest artifact checkbox', () => {
const fullPath = 'gitlab-org/gitlab';
const helpPagePath = '/help/ci/pipelines/job_artifacts';
const findCheckbox = () => wrapper.find(GlFormCheckbox);
const findHelpLink = () => wrapper.find(GlLink);
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findHelpLink = () => wrapper.findComponent(GlLink);
const createComponent = (handlers) => {
requestHandlers = {

View File

@ -31,9 +31,9 @@ describe('RecoveryCodes', () => {
};
const queryByText = (text, options) => within(wrapper.element).queryByText(text, options);
const findAlert = () => wrapper.find(GlAlert);
const findAlert = () => wrapper.findComponent(GlAlert);
const findRecoveryCodes = () => wrapper.findByTestId('recovery-codes');
const findCopyButton = () => wrapper.find(ClipboardButton);
const findCopyButton = () => wrapper.findComponent(ClipboardButton);
const findButtonByText = (text) =>
wrapper.findAll(GlButton).wrappers.find((buttonWrapper) => buttonWrapper.text() === text);
const findDownloadButton = () => findButtonByText('Download codes');

View File

@ -10,7 +10,7 @@ describe('initRecoveryCodes', () => {
let el;
let wrapper;
const findRecoveryCodesComponent = () => wrapper.find(RecoveryCodes);
const findRecoveryCodesComponent = () => wrapper.findComponent(RecoveryCodes);
beforeEach(() => {
el = document.createElement('div');

View File

@ -42,7 +42,7 @@ describe('BadgeSettings component', () => {
button.vm.$emit('click');
await nextTick();
const modal = wrapper.find(GlModal);
const modal = wrapper.findComponent(GlModal);
expect(modal.isVisible()).toBe(true);
});
@ -51,7 +51,7 @@ describe('BadgeSettings component', () => {
});
it('displays badge list', () => {
expect(wrapper.find(BadgeList).isVisible()).toBe(true);
expect(wrapper.findComponent(BadgeList).isVisible()).toBe(true);
});
describe('when editing', () => {
@ -64,7 +64,7 @@ describe('BadgeSettings component', () => {
});
it('displays no badge list', () => {
expect(wrapper.find(BadgeList).isVisible()).toBe(false);
expect(wrapper.findComponent(BadgeList).isVisible()).toBe(false);
});
});
});

View File

@ -61,7 +61,7 @@ describe('Batch comments draft note component', () => {
createComponent();
expect(wrapper.findComponent(GlBadge).exists()).toBe(true);
const note = wrapper.find(NoteableNote);
const note = wrapper.findComponent(NoteableNote);
expect(note.exists()).toBe(true);
expect(note.props().note).toEqual(draft);
@ -122,7 +122,7 @@ describe('Batch comments draft note component', () => {
describe('update', () => {
it('dispatches updateDraft', async () => {
createComponent();
const note = wrapper.find(NoteableNote);
const note = wrapper.findComponent(NoteableNote);
note.vm.$emit('handleEdit');
@ -147,7 +147,7 @@ describe('Batch comments draft note component', () => {
createComponent();
jest.spyOn(window, 'confirm').mockImplementation(() => true);
const note = wrapper.find(NoteableNote);
const note = wrapper.findComponent(NoteableNote);
note.vm.$emit('handleDeleteNote', draft);

View File

@ -34,6 +34,6 @@ describe('Batch comments publish dropdown component', () => {
it('renders draft count in dropdown title', () => {
createComponent();
expect(wrapper.find(GlDropdown).props('headerText')).toEqual('2 pending comments');
expect(wrapper.findComponent(GlDropdown).props('headerText')).toEqual('2 pending comments');
});
});

View File

@ -40,7 +40,7 @@ describe('Captcha Modal', () => {
});
const findGlModal = () => {
const glModal = wrapper.find(GlModal);
const glModal = wrapper.findComponent(GlModal);
jest.spyOn(glModal.vm, 'show').mockImplementation(() => glModal.vm.$emit('shown'));
jest

View File

@ -39,7 +39,7 @@ describe('LockPopovers', () => {
wrapper = mountExtended(LockPopovers);
};
const findPopover = () => extendedWrapper(wrapper.find(GlPopover));
const findPopover = () => extendedWrapper(wrapper.findComponent(GlPopover));
const findByTextInPopover = (text, options) =>
findPopover().findByText((_, element) => element.textContent === text, options);

View File

@ -36,9 +36,9 @@ describe('CI Lint', () => {
});
};
const findEditor = () => wrapper.find(SourceEditor);
const findAlert = () => wrapper.find(GlAlert);
const findCiLintResults = () => wrapper.find(CiLintResults);
const findEditor = () => wrapper.findComponent(SourceEditor);
const findAlert = () => wrapper.findComponent(GlAlert);
const findCiLintResults = () => wrapper.findComponent(CiLintResults);
const findValidateBtn = () => wrapper.find('[data-testid="ci-lint-validate"]');
const findClearBtn = () => wrapper.find('[data-testid="ci-lint-clear"]');

View File

@ -83,7 +83,9 @@ describe('SecureFilesList', () => {
const [secureFile] = secureFiles;
expect(findCell(0, 0).text()).toBe(secureFile.name);
expect(findCell(0, 1).find(TimeAgoTooltip).props('time')).toBe(secureFile.created_at);
expect(findCell(0, 1).findComponent(TimeAgoTooltip).props('time')).toBe(
secureFile.created_at,
);
});
describe('event tracking', () => {

View File

@ -16,13 +16,13 @@ describe('TriggersList', () => {
});
};
const findTable = () => wrapper.find(GlTable);
const findTable = () => wrapper.findComponent(GlTable);
const findHeaderAt = (i) => wrapper.findAll('thead th').at(i);
const findRows = () => wrapper.findAll('tbody tr');
const findRowAt = (i) => findRows().at(i);
const findCell = (i, col) => findRowAt(i).findAll('td').at(col);
const findClipboardBtn = (i) => findCell(i, 0).find(ClipboardButton);
const findInvalidBadge = (i) => findCell(i, 0).find(GlBadge);
const findClipboardBtn = (i) => findCell(i, 0).findComponent(ClipboardButton);
const findInvalidBadge = (i) => findCell(i, 0).findComponent(GlBadge);
const findEditBtn = (i) => findRowAt(i).find('[data-testid="edit-btn"]');
const findRevokeBtn = (i) => findRowAt(i).find('[data-testid="trigger_revoke_button"]');
@ -65,7 +65,7 @@ describe('TriggersList', () => {
it('displays a time ago label when last used', () => {
expect(findCell(0, 3).text()).toBe('Never');
expect(findCell(1, 3).find(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed);
expect(findCell(1, 3).findComponent(TimeAgoTooltip).props('time')).toBe(triggers[1].lastUsed);
});
it('displays actions in a rows', () => {

View File

@ -58,9 +58,9 @@ describe('Ci variable modal', () => {
});
};
const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown);
const findReferenceWarning = () => wrapper.findByTestId('contains-variable-reference');
const findModal = () => wrapper.find(ModalStub);
const findModal = () => wrapper.findComponent(ModalStub);
const findAWSTip = () => wrapper.findByTestId('aws-guidance-tip');
const findAddorUpdateButton = () => wrapper.findByTestId('ciUpdateOrAddVariableBtn');
const deleteVariableButton = () =>
@ -72,7 +72,8 @@ describe('Ci variable modal', () => {
const findMaskedVariableCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox');
const findValueField = () => wrapper.find('#ci-variable-value');
const findEnvScopeLink = () => wrapper.findByTestId('environment-scope-link');
const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').find(GlFormInput);
const findEnvScopeInput = () =>
wrapper.findByTestId('environment-scope').findComponent(GlFormInput);
const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type');
afterEach(() => {

View File

@ -18,7 +18,7 @@ describe('Ci Variable Popover', () => {
});
};
const findButton = () => wrapper.find(GlButton);
const findButton = () => wrapper.findComponent(GlButton);
beforeEach(() => {
createComponent();

View File

@ -40,8 +40,8 @@ describe('Ci variable modal', () => {
});
};
const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown);
const findModal = () => wrapper.find(ModalStub);
const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown);
const findModal = () => wrapper.findComponent(ModalStub);
const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]');
const deleteVariableButton = () =>
findModal()
@ -213,7 +213,7 @@ describe('Ci variable modal', () => {
const environmentScopeInput = wrapper
.find('[data-testid="environment-scope"]')
.find(GlFormInput);
.findComponent(GlFormInput);
expect(findCiEnvironmentsDropdown().exists()).toBe(false);
expect(environmentScopeInput.attributes('readonly')).toBe('readonly');
});

View File

@ -36,7 +36,7 @@ describe('AgentTable', () => {
const findAgentLink = (at) => wrapper.findAllByTestId('cluster-agent-name-link').at(at);
const findStatusText = (at) => wrapper.findAllByTestId('cluster-agent-connection-status').at(at);
const findStatusIcon = (at) => findStatusText(at).find(GlIcon);
const findStatusIcon = (at) => findStatusText(at).findComponent(GlIcon);
const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at);
const findVersionText = (at) => wrapper.findAllByTestId('cluster-agent-version').at(at);
const findConfiguration = (at) =>
@ -113,7 +113,7 @@ describe('AgentTable', () => {
texts,
lineNumber,
}) => {
const findIcon = () => findVersionText(lineNumber).find(GlIcon);
const findIcon = () => findVersionText(lineNumber).findComponent(GlIcon);
const findPopover = () => wrapper.findByTestId(`popover-${agent}`);
const versionWarning = versionMismatch || versionOutdated;
@ -151,7 +151,7 @@ describe('AgentTable', () => {
`(
'displays config file path as "$agentPath" at line $lineNumber',
({ agentConfig, link, lineNumber }) => {
const findLink = findConfiguration(lineNumber).find(GlLink);
const findLink = findConfiguration(lineNumber).findComponent(GlLink);
expect(findLink.attributes('href')).toBe(link);
expect(findConfiguration(lineNumber).text()).toBe(agentConfig);

View File

@ -334,7 +334,7 @@ describe('Agents', () => {
});
it('displays a loading icon', () => {
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
});

View File

@ -46,7 +46,7 @@ describe('ClustersAncestorNotice', () => {
});
it('displays link', () => {
expect(wrapper.find(GlLink).exists()).toBe(true);
expect(wrapper.findComponent(GlLink).exists()).toBe(true);
});
});
});

View File

@ -142,7 +142,7 @@ describe('Clusters', () => {
({ lineNumber, result }) => {
const statuses = findStatuses();
const status = statuses.at(lineNumber);
expect(status.find(GlLoadingIcon).exists()).toBe(result);
expect(status.findComponent(GlLoadingIcon).exists()).toBe(result);
},
);
});

View File

@ -11,7 +11,7 @@ describe('NodeErrorHelpText', () => {
await nextTick();
};
const findPopover = () => wrapper.find(GlPopover);
const findPopover = () => wrapper.findComponent(GlPopover);
afterEach(() => {
wrapper.destroy();

View File

@ -63,7 +63,7 @@ describe('Code navigation app component', () => {
it('hides popover when no definition set', () => {
factory();
expect(wrapper.find(Popover).exists()).toBe(false);
expect(wrapper.findComponent(Popover).exists()).toBe(false);
});
it('renders popover when definition set', () => {
@ -73,7 +73,7 @@ describe('Code navigation app component', () => {
currentBlobPath: 'index.js',
});
expect(wrapper.find(Popover).exists()).toBe(true);
expect(wrapper.findComponent(Popover).exists()).toBe(true);
});
it('calls showDefinition when clicking blob viewer', () => {

View File

@ -115,8 +115,8 @@ describe('Code navigation popover component', () => {
definitionPathPrefix: DEFINITION_PATH_PREFIX,
});
expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(true);
expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(false);
expect(wrapper.findComponent({ ref: 'code-output' }).exists()).toBe(true);
expect(wrapper.findComponent({ ref: 'doc-output' }).exists()).toBe(false);
});
});
@ -128,8 +128,8 @@ describe('Code navigation popover component', () => {
definitionPathPrefix: DEFINITION_PATH_PREFIX,
});
expect(wrapper.find({ ref: 'code-output' }).exists()).toBe(false);
expect(wrapper.find({ ref: 'doc-output' }).exists()).toBe(true);
expect(wrapper.findComponent({ ref: 'code-output' }).exists()).toBe(false);
expect(wrapper.findComponent({ ref: 'doc-output' }).exists()).toBe(true);
});
});
});

View File

@ -37,9 +37,9 @@ describe('Commit pipeline status component', () => {
});
};
const findLoader = () => wrapper.find(GlLoadingIcon);
const findLoader = () => wrapper.findComponent(GlLoadingIcon);
const findLink = () => wrapper.find('a');
const findCiIcon = () => findLink().find(CiIcon);
const findCiIcon = () => findLink().findComponent(CiIcon);
afterEach(() => {
wrapper.destroy();

View File

@ -36,12 +36,12 @@ describe('Confidential merge request project dropdown component', () => {
it('shows lock icon', () => {
factory();
expect(vm.find(GlDropdown).props('icon')).toBe('lock');
expect(vm.findComponent(GlDropdown).props('icon')).toBe('lock');
});
it('has dropdown text', () => {
factory();
expect(vm.find(GlDropdown).props('text')).toBe('Select private project');
expect(vm.findComponent(GlDropdown).props('text')).toBe('Select private project');
});
});

View File

@ -62,7 +62,7 @@ describe.each`
};
const selectFile = async (file) => {
const input = wrapper.find({ ref: 'fileSelector' });
const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });

View File

@ -27,7 +27,7 @@ describe('content_editor/components/toolbar_image_button', () => {
const findDropdown = () => wrapper.findComponent(GlDropdown);
const selectFile = async (file) => {
const input = wrapper.find({ ref: 'fileSelector' });
const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });

View File

@ -30,7 +30,7 @@ describe('content_editor/components/toolbar_link_button', () => {
const findRemoveLinkButton = () => wrapper.findByText('Remove link');
const selectFile = async (file) => {
const input = wrapper.find({ ref: 'fileSelector' });
const input = wrapper.findComponent({ ref: 'fileSelector' });
// override the property definition because `input.files` isn't directly modifyable
Object.defineProperty(input.element, 'files', { value: [file], writable: true });

View File

@ -104,7 +104,7 @@ describe('content/components/wrappers/code_block', () => {
it('does not render a preview if showPreview: false', async () => {
createWrapper({ language: 'plantuml', isDiagram: true, showPreview: false });
expect(wrapper.find({ ref: 'diagramContainer' }).exists()).toBe(false);
expect(wrapper.findComponent({ ref: 'diagramContainer' }).exists()).toBe(false);
});
it('does not update preview when diagram is not active', async () => {
@ -134,7 +134,7 @@ describe('content/components/wrappers/code_block', () => {
await nextTick();
expect(wrapper.find('img').attributes('src')).toBe('url/to/some/diagram');
expect(wrapper.find(SandboxedMermaid).exists()).toBe(false);
expect(wrapper.findComponent(SandboxedMermaid).exists()).toBe(false);
});
it('renders an iframe with preview for a mermaid diagram', async () => {
@ -143,7 +143,7 @@ describe('content/components/wrappers/code_block', () => {
await emitEditorEvent({ event: 'transaction', tiptapEditor });
await nextTick();
expect(wrapper.find(SandboxedMermaid).props('source')).toBe('');
expect(wrapper.findComponent(SandboxedMermaid).props('source')).toBe('');
expect(wrapper.find('img').exists()).toBe(false);
});
});

View File

@ -298,7 +298,7 @@ describe('Reusable form component', () => {
`(
'should render the correct component for #$id with the value "$value"',
({ index, id, component, value }) => {
const findFormElement = () => findFormGroup(index).find(component);
const findFormElement = () => findFormGroup(index).findComponent(component);
expect(findFormElement().attributes('id')).toBe(id);
expect(findFormElement().attributes('value')).toBe(value);
@ -307,7 +307,8 @@ describe('Reusable form component', () => {
it('should render a checked GlFormCheckbox for #active', () => {
const activeCheckboxIndex = 6;
const findFormElement = () => findFormGroup(activeCheckboxIndex).find(GlFormCheckbox);
const findFormElement = () =>
findFormGroup(activeCheckboxIndex).findComponent(GlFormCheckbox);
expect(findFormElement().attributes('id')).toBe('active');
expect(findFormElement().attributes('checked')).toBe('true');

View File

@ -201,7 +201,7 @@ describe('Value stream analytics component', () => {
it('renders the stage table with a loading icon', () => {
const tableWrapper = findStageTable();
expect(tableWrapper.exists()).toBe(true);
expect(tableWrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(tableWrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('renders the path navigation loading state', () => {

View File

@ -56,7 +56,9 @@ describe('Project PathNavigation', () => {
describe('displays correctly', () => {
it('has the correct props', () => {
expect(wrapper.find(GlPath).props('items')).toMatchObject(transformedProjectStagePathData);
expect(wrapper.findComponent(GlPath).props('items')).toMatchObject(
transformedProjectStagePathData,
);
});
it('contains all the expected stages', () => {
@ -69,11 +71,11 @@ describe('Project PathNavigation', () => {
describe('loading', () => {
describe('is false', () => {
it('displays the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(true);
expect(wrapper.findComponent(GlPath).exists()).toBe(true);
});
it('hides the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(false);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(false);
});
it('renders each stage', () => {
@ -112,11 +114,11 @@ describe('Project PathNavigation', () => {
});
it('hides the gl-path component', () => {
expect(wrapper.find(GlPath).exists()).toBe(false);
expect(wrapper.findComponent(GlPath).exists()).toBe(false);
});
it('displays the gl-skeleton-loading component', () => {
expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true);
expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true);
});
});
});

View File

@ -42,7 +42,7 @@ describe('Deploy freeze modal', () => {
wrapper.find('#deploy-freeze-start').trigger('input');
wrapper.find('#deploy-freeze-end').trigger('input');
wrapper.find(TimezoneDropdown).trigger('input');
wrapper.findComponent(TimezoneDropdown).trigger('input');
};
afterEach(() => {

View File

@ -31,11 +31,11 @@ describe('Deploy freeze settings', () => {
describe('Deploy freeze table contains components', () => {
it('contains deploy freeze table', () => {
expect(wrapper.find(DeployFreezeTable).exists()).toBe(true);
expect(wrapper.findComponent(DeployFreezeTable).exists()).toBe(true);
});
it('contains deploy freeze modal', () => {
expect(wrapper.find(DeployFreezeModal).exists()).toBe(true);
expect(wrapper.findComponent(DeployFreezeModal).exists()).toBe(true);
});
});
});

View File

@ -96,7 +96,7 @@ describe('Deploy freeze timezone dropdown', () => {
});
it('renders selected time zone as dropdown label', () => {
expect(wrapper.find(GlDropdown).vm.text).toBe('Alaska');
expect(wrapper.findComponent(GlDropdown).vm.text).toBe('Alaska');
});
});
});

View File

@ -30,7 +30,7 @@ const UPDATED_COMMIT_URL = `${TEST_HOST}/COMMIT/NEW`;
Vue.use(Vuex);
function getCollapsedFilesWarning(wrapper) {
return wrapper.find(CollapsedFilesWarning);
return wrapper.findComponent(CollapsedFilesWarning);
}
describe('diffs/components/app', () => {
@ -167,7 +167,7 @@ describe('diffs/components/app', () => {
state.diffs.isLoading = true;
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('displays loading icon on batch loading', () => {
@ -175,13 +175,13 @@ describe('diffs/components/app', () => {
state.diffs.batchLoadingState = 'loading';
});
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('displays diffs container when not loading', () => {
createComponent();
expect(wrapper.find(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
expect(wrapper.find('#diffs').exists()).toBe(true);
});
@ -263,7 +263,7 @@ describe('diffs/components/app', () => {
it('renders empty state when no diff files exist', () => {
createComponent();
expect(wrapper.find(NoChanges).exists()).toBe(true);
expect(wrapper.findComponent(NoChanges).exists()).toBe(true);
});
it('does not render empty state when diff files exist', () => {
@ -273,7 +273,7 @@ describe('diffs/components/app', () => {
});
});
expect(wrapper.find(NoChanges).exists()).toBe(false);
expect(wrapper.findComponent(NoChanges).exists()).toBe(false);
expect(wrapper.findAll(DiffFile).length).toBe(1);
});
});
@ -487,8 +487,8 @@ describe('diffs/components/app', () => {
state.diffs.mergeRequestDiff = mergeRequestDiff;
});
expect(wrapper.find(CompareVersions).exists()).toBe(true);
expect(wrapper.find(CompareVersions).props()).toEqual(
expect(wrapper.findComponent(CompareVersions).exists()).toBe(true);
expect(wrapper.findComponent(CompareVersions).props()).toEqual(
expect.objectContaining({
diffFilesCountText: null,
}),
@ -506,8 +506,8 @@ describe('diffs/components/app', () => {
state.diffs.size = 1;
});
expect(wrapper.find(HiddenFilesWarning).exists()).toBe(true);
expect(wrapper.find(HiddenFilesWarning).props()).toEqual(
expect(wrapper.findComponent(HiddenFilesWarning).exists()).toBe(true);
expect(wrapper.findComponent(HiddenFilesWarning).props()).toEqual(
expect.objectContaining({
total: '5',
plainDiffPath: 'plain diff path',
@ -547,7 +547,7 @@ describe('diffs/components/app', () => {
};
});
expect(wrapper.find(CommitWidget).exists()).toBe(true);
expect(wrapper.findComponent(CommitWidget).exists()).toBe(true);
});
it('should display diff file if there are diff files', () => {
@ -555,13 +555,13 @@ describe('diffs/components/app', () => {
state.diffs.diffFiles.push({ sha: '123' });
});
expect(wrapper.find(DiffFile).exists()).toBe(true);
expect(wrapper.findComponent(DiffFile).exists()).toBe(true);
});
it("doesn't render tree list when no changes exist", () => {
createComponent();
expect(wrapper.find(TreeList).exists()).toBe(false);
expect(wrapper.findComponent(TreeList).exists()).toBe(false);
});
it('should render tree list', () => {
@ -569,7 +569,7 @@ describe('diffs/components/app', () => {
state.diffs.diffFiles = [{ file_hash: '111', file_path: '111.js' }];
});
expect(wrapper.find(TreeList).exists()).toBe(true);
expect(wrapper.findComponent(TreeList).exists()).toBe(true);
});
});
@ -641,7 +641,7 @@ describe('diffs/components/app', () => {
describe('pagination', () => {
const fileByFileNav = () => wrapper.find('[data-testid="file-by-file-navigation"]');
const paginator = () => fileByFileNav().find(GlPagination);
const paginator = () => fileByFileNav().findComponent(GlPagination);
it('sets previous button as disabled', async () => {
createComponent({ fileByFileUserPreference: true }, ({ state }) => {

View File

@ -28,8 +28,8 @@ describe('CollapsedFilesWarning', () => {
Vue.use(Vuex);
const getAlertActionButton = () =>
wrapper.find(CollapsedFilesWarning).find('button.gl-alert-action:first-child');
const getAlertCloseButton = () => wrapper.find(CollapsedFilesWarning).find('button');
wrapper.findComponent(CollapsedFilesWarning).find('button.gl-alert-action:first-child');
const getAlertCloseButton = () => wrapper.findComponent(CollapsedFilesWarning).find('button');
const createComponent = (props = {}, { full } = { full: false }) => {
const mounter = full ? mount : shallowMount;

View File

@ -27,7 +27,7 @@ describe('diffs/components/commit_item', () => {
const getAvatarElement = () => wrapper.find('.user-avatar-link');
const getCommitterElement = () => wrapper.find('.committer');
const getCommitActionsElement = () => wrapper.find('.commit-actions');
const getCommitPipelineStatus = () => wrapper.find(CommitPipelineStatus);
const getCommitPipelineStatus = () => wrapper.findComponent(CommitPipelineStatus);
const mountComponent = (propsData) => {
wrapper = mount(Component, {

View File

@ -12,7 +12,7 @@ describe('diffs/components/commit_widget', () => {
});
it('renders commit item', () => {
const commitElement = wrapper.find(CommitItem);
const commitElement = wrapper.findComponent(CommitItem);
expect(commitElement.exists()).toBe(true);
});

View File

@ -20,24 +20,24 @@ describe('DiffCommentCell', () => {
it('renders discussions if line has discussions', () => {
const wrapper = createWrapper({ renderDiscussion: true });
expect(wrapper.find(DiffDiscussions).exists()).toBe(true);
expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(true);
});
it('does not render discussions if line has no discussions', () => {
const wrapper = createWrapper();
expect(wrapper.find(DiffDiscussions).exists()).toBe(false);
expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(false);
});
it('renders discussion reply if line has no draft', () => {
const wrapper = createWrapper();
expect(wrapper.find(DiffDiscussionReply).exists()).toBe(true);
expect(wrapper.findComponent(DiffDiscussionReply).exists()).toBe(true);
});
it('does not render discussion reply if line has draft', () => {
const wrapper = createWrapper({ hasDraft: true });
expect(wrapper.find(DiffDiscussionReply).exists()).toBe(false);
expect(wrapper.findComponent(DiffDiscussionReply).exists()).toBe(false);
});
});

View File

@ -110,13 +110,13 @@ describe('DiffContent', () => {
props: { diffFile: textDiffFile },
});
expect(wrapper.find(DiffView).exists()).toBe(true);
expect(wrapper.findComponent(DiffView).exists()).toBe(true);
});
it('renders rendering more lines loading icon', () => {
createComponent({ props: { diffFile: { ...textDiffFile, renderingLines: true } } });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
});
@ -133,7 +133,7 @@ describe('DiffContent', () => {
props: { diffFile: { ...emptyDiffFile, viewer: { name: diffViewerModes.no_preview } } },
});
expect(wrapper.find(NoPreviewViewer).exists()).toBe(true);
expect(wrapper.findComponent(NoPreviewViewer).exists()).toBe(true);
});
it('should render not diffable view if viewer set to non_diffable', () => {
@ -141,7 +141,7 @@ describe('DiffContent', () => {
props: { diffFile: { ...emptyDiffFile, viewer: { name: diffViewerModes.not_diffable } } },
});
expect(wrapper.find(NotDiffableViewer).exists()).toBe(true);
expect(wrapper.findComponent(NotDiffableViewer).exists()).toBe(true);
});
});
@ -156,7 +156,7 @@ describe('DiffContent', () => {
},
});
expect(wrapper.find(DiffDiscussions).exists()).toBe(true);
expect(wrapper.findComponent(DiffDiscussions).exists()).toBe(true);
});
it('emits saveDiffDiscussion when note-form emits `handleFormUpdate`', () => {
@ -169,7 +169,7 @@ describe('DiffContent', () => {
},
});
wrapper.find(NoteForm).vm.$emit('handleFormUpdate', noteStub);
wrapper.findComponent(NoteForm).vm.$emit('handleFormUpdate', noteStub);
expect(saveDiffDiscussionMock).toHaveBeenCalledWith(expect.any(Object), {
note: noteStub,
formData: {

View File

@ -64,7 +64,7 @@ describe('DiffDiscussionReply', () => {
hasForm: false,
});
expect(wrapper.find(ReplyPlaceholder).exists()).toBe(true);
expect(wrapper.findComponent(ReplyPlaceholder).exists()).toBe(true);
});
});
@ -83,6 +83,6 @@ describe('DiffDiscussionReply', () => {
hasForm: false,
});
expect(wrapper.find(NoteSignedOutWidget).exists()).toBe(true);
expect(wrapper.findComponent(NoteSignedOutWidget).exists()).toBe(true);
});
});

View File

@ -32,9 +32,9 @@ describe('DiffDiscussions', () => {
it('should have notes list', () => {
createComponent();
expect(wrapper.find(NoteableDiscussion).exists()).toBe(true);
expect(wrapper.find(DiscussionNotes).exists()).toBe(true);
expect(wrapper.find(DiscussionNotes).findAll(TimelineEntryItem).length).toBe(
expect(wrapper.findComponent(NoteableDiscussion).exists()).toBe(true);
expect(wrapper.findComponent(DiscussionNotes).exists()).toBe(true);
expect(wrapper.findComponent(DiscussionNotes).findAll(TimelineEntryItem).length).toBe(
discussionsMockData.notes.length,
);
});
@ -48,7 +48,7 @@ describe('DiffDiscussions', () => {
const diffNotesToggle = findDiffNotesToggle();
expect(diffNotesToggle.exists()).toBe(true);
expect(diffNotesToggle.find(GlIcon).exists()).toBe(true);
expect(diffNotesToggle.findComponent(GlIcon).exists()).toBe(true);
expect(diffNotesToggle.classes('diff-notes-collapse')).toBe(true);
});
@ -80,12 +80,12 @@ describe('DiffDiscussions', () => {
discussions[0].expanded = false;
createComponent({ discussions, shouldCollapseDiscussions: true });
expect(wrapper.find(NoteableDiscussion).isVisible()).toBe(false);
expect(wrapper.findComponent(NoteableDiscussion).isVisible()).toBe(false);
});
it('renders badge on avatar', () => {
createComponent({ renderAvatarBadge: true });
const noteableDiscussion = wrapper.find(NoteableDiscussion);
const noteableDiscussion = wrapper.findComponent(NoteableDiscussion);
expect(noteableDiscussion.find('.design-note-pin').exists()).toBe(true);
expect(noteableDiscussion.find('.design-note-pin').text().trim()).toBe('1');

View File

@ -76,18 +76,19 @@ describe('DiffFileHeader component', () => {
wrapper.destroy();
});
const findHeader = () => wrapper.find({ ref: 'header' });
const findTitleLink = () => wrapper.find({ ref: 'titleWrapper' });
const findExpandButton = () => wrapper.find({ ref: 'expandDiffToFullFileButton' });
const findHeader = () => wrapper.findComponent({ ref: 'header' });
const findTitleLink = () => wrapper.findComponent({ ref: 'titleWrapper' });
const findExpandButton = () => wrapper.findComponent({ ref: 'expandDiffToFullFileButton' });
const findFileActions = () => wrapper.find('.file-actions');
const findModeChangedLine = () => wrapper.find({ ref: 'fileMode' });
const findModeChangedLine = () => wrapper.findComponent({ ref: 'fileMode' });
const findLfsLabel = () => wrapper.find('[data-testid="label-lfs"]');
const findToggleDiscussionsButton = () => wrapper.find({ ref: 'toggleDiscussionsButton' });
const findExternalLink = () => wrapper.find({ ref: 'externalLink' });
const findReplacedFileButton = () => wrapper.find({ ref: 'replacedFileButton' });
const findViewFileButton = () => wrapper.find({ ref: 'viewButton' });
const findCollapseIcon = () => wrapper.find({ ref: 'collapseIcon' });
const findEditButton = () => wrapper.find({ ref: 'editButton' });
const findToggleDiscussionsButton = () =>
wrapper.findComponent({ ref: 'toggleDiscussionsButton' });
const findExternalLink = () => wrapper.findComponent({ ref: 'externalLink' });
const findReplacedFileButton = () => wrapper.findComponent({ ref: 'replacedFileButton' });
const findViewFileButton = () => wrapper.findComponent({ ref: 'viewButton' });
const findCollapseIcon = () => wrapper.findComponent({ ref: 'collapseIcon' });
const findEditButton = () => wrapper.findComponent({ ref: 'editButton' });
const findReviewFileCheckbox = () => wrapper.find("[data-testid='fileReviewCheckbox']");
const createComponent = ({ props, options = {} } = {}) => {
@ -153,7 +154,7 @@ describe('DiffFileHeader component', () => {
});
it('displays a copy to clipboard button', () => {
expect(wrapper.find(ClipboardButton).exists()).toBe(true);
expect(wrapper.findComponent(ClipboardButton).exists()).toBe(true);
});
it('triggers the copy to clipboard tracking event', () => {

Some files were not shown because too many files have changed in this diff Show More