diff options
80 files changed, 570 insertions, 337 deletions
diff --git a/app/assets/javascripts/notes/components/note_body.vue b/app/assets/javascripts/notes/components/note_body.vue index cc74c2ee605..f1c41eea428 100644 --- a/app/assets/javascripts/notes/components/note_body.vue +++ b/app/assets/javascripts/notes/components/note_body.vue @@ -8,7 +8,6 @@ import { __ } from '~/locale'; import '~/behaviors/markdown/render_gfm'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; import autosave from '../mixins/autosave'; -import { INTERNAL_NOTE_CLASSES } from '../constants'; import noteAttachment from './note_attachment.vue'; import noteAwardsList from './note_awards_list.vue'; import noteEditedText from './note_edited_text.vue'; @@ -55,11 +54,6 @@ export default { required: false, default: '', }, - isInternalNote: { - type: Boolean, - required: false, - default: false, - }, }, computed: { ...mapGetters(['getDiscussion', 'suggestionsCount', 'getSuggestionsFilePaths']), @@ -101,12 +95,6 @@ export default { return escape(suggestion); }, - internalNoteContainerClasses() { - if (this.isInternalNote && !this.isEditing) { - return INTERNAL_NOTE_CLASSES; - } - return ''; - }, }, mounted() { this.renderGFM(); @@ -179,54 +167,52 @@ export default { }" class="note-body" > - <div :class="internalNoteContainerClasses" data-testid="note-internal-container"> - <suggestions - v-if="hasSuggestion && !isEditing" - :suggestions="note.suggestions" - :suggestions-count="suggestionsCount" - :batch-suggestions-info="batchSuggestionsInfo" - :note-html="note.note_html" - :line-type="lineType" - :help-page-path="helpPagePath" - :default-commit-message="commitMessage" - :failed-to-load-metadata="failedToLoadMetadata" - @apply="applySuggestion" - @applyBatch="applySuggestionBatch" - @addToBatch="addSuggestionToBatch" - @removeFromBatch="removeSuggestionFromBatch" - /> - <div v-else v-safe-html:[$options.safeHtmlConfig]="note.note_html" class="note-text md"></div> - <note-form - v-if="isEditing" - ref="noteForm" - :note-body="noteBody" - :note-id="note.id" - :line="line" - :note="note" - :save-button-title="saveButtonTitle" - :help-page-path="helpPagePath" - :discussion="discussion" - :resolve-discussion="note.resolve_discussion" - @handleFormUpdate="handleFormUpdate" - @cancelForm="formCancelHandler" - /> - <!-- eslint-disable vue/no-mutating-props --> - <textarea - v-if="canEdit" - v-model="note.note" - :data-update-url="note.path" - class="hidden js-task-list-field" - dir="auto" - ></textarea> - <!-- eslint-enable vue/no-mutating-props --> - <note-edited-text - v-if="note.last_edited_at" - :edited-at="note.last_edited_at" - :edited-by="note.last_edited_by" - action-text="Edited" - class="note_edited_ago" - /> - </div> + <suggestions + v-if="hasSuggestion && !isEditing" + :suggestions="note.suggestions" + :suggestions-count="suggestionsCount" + :batch-suggestions-info="batchSuggestionsInfo" + :note-html="note.note_html" + :line-type="lineType" + :help-page-path="helpPagePath" + :default-commit-message="commitMessage" + :failed-to-load-metadata="failedToLoadMetadata" + @apply="applySuggestion" + @applyBatch="applySuggestionBatch" + @addToBatch="addSuggestionToBatch" + @removeFromBatch="removeSuggestionFromBatch" + /> + <div v-else v-safe-html:[$options.safeHtmlConfig]="note.note_html" class="note-text md"></div> + <note-form + v-if="isEditing" + ref="noteForm" + :note-body="noteBody" + :note-id="note.id" + :line="line" + :note="note" + :save-button-title="saveButtonTitle" + :help-page-path="helpPagePath" + :discussion="discussion" + :resolve-discussion="note.resolve_discussion" + @handleFormUpdate="handleFormUpdate" + @cancelForm="formCancelHandler" + /> + <!-- eslint-disable vue/no-mutating-props --> + <textarea + v-if="canEdit" + v-model="note.note" + :data-update-url="note.path" + class="hidden js-task-list-field" + dir="auto" + ></textarea> + <!-- eslint-enable vue/no-mutating-props --> + <note-edited-text + v-if="note.last_edited_at" + :edited-at="note.last_edited_at" + :edited-by="note.last_edited_by" + action-text="Edited" + class="note_edited_ago" + /> <note-awards-list v-if="note.award_emoji && note.award_emoji.length" :note-id="note.id" diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue index 095ab5ddb0f..875cfff74fe 100644 --- a/app/assets/javascripts/notes/components/noteable_note.vue +++ b/app/assets/javascripts/notes/components/noteable_note.vue @@ -406,7 +406,7 @@ export default { <template> <timeline-entry-item :id="noteAnchorId" - :class="classNameBindings" + :class="{ ...classNameBindings, 'internal-note': note.confidential }" :data-award-url="note.toggle_award_path" :data-note-id="note.id" class="note note-wrapper" @@ -506,7 +506,6 @@ export default { ref="noteBody" :note="note" :can-edit="note.current_user.can_edit" - :is-internal-note="note.confidential" :line="line" :file="diffFile" :is-editing="isEditing" diff --git a/app/assets/javascripts/notes/constants.js b/app/assets/javascripts/notes/constants.js index 3317f4e2383..a5f459c8910 100644 --- a/app/assets/javascripts/notes/constants.js +++ b/app/assets/javascripts/notes/constants.js @@ -51,5 +51,3 @@ export const toggleStateErrorMessage = { [REOPENED]: __('Something went wrong while closing the merge request. Please try again later.'), }, }; - -export const INTERNAL_NOTE_CLASSES = ['gl-bg-orange-50', 'gl-px-4', 'gl-py-2']; diff --git a/app/assets/stylesheets/framework/timeline.scss b/app/assets/stylesheets/framework/timeline.scss index 086b83b13e0..43effbdd7d7 100644 --- a/app/assets/stylesheets/framework/timeline.scss +++ b/app/assets/stylesheets/framework/timeline.scss @@ -35,6 +35,10 @@ background-color: $white; } + &:not(.note-form).internal-note { + background-color: $orange-50; + } + .timeline-entry-inner { position: relative; } diff --git a/app/assets/stylesheets/themes/dark_mode_overrides.scss b/app/assets/stylesheets/themes/dark_mode_overrides.scss index 81bd49eb676..92740aaf89e 100644 --- a/app/assets/stylesheets/themes/dark_mode_overrides.scss +++ b/app/assets/stylesheets/themes/dark_mode_overrides.scss @@ -160,3 +160,8 @@ body.gl-dark { background-color: $gray-200; } } + +.timeline-entry.internal-note:not(.note-form) { + // soften on darkmode + background-color: mix($gray-50, $orange-50, 75%); +} diff --git a/app/finders/fork_targets_finder.rb b/app/finders/fork_targets_finder.rb index 0b5dfb16572..e129fde3748 100644 --- a/app/finders/fork_targets_finder.rb +++ b/app/finders/fork_targets_finder.rb @@ -6,17 +6,39 @@ class ForkTargetsFinder @user = user end - # rubocop: disable CodeReuse/ActiveRecord def execute(options = {}) - return ::Namespace.where(id: user.forkable_namespaces).sort_by_type unless options[:only_groups] + return previous_execute(options) unless Feature.enabled?(:searchable_fork_targets) - ::Group.where(id: user.manageable_groups(include_groups_with_developer_maintainer_access: true)) + items = fork_targets(options) + + by_search(items, options) end - # rubocop: enable CodeReuse/ActiveRecord private attr_reader :project, :user + + def by_search(items, options) + return items if options[:search].blank? + + items.search(options[:search]) + end + + def fork_targets(options) + if options[:only_groups] + user.manageable_groups(include_groups_with_developer_maintainer_access: true) + else + user.forkable_namespaces.sort_by_type + end + end + + # rubocop: disable CodeReuse/ActiveRecord + def previous_execute(options = {}) + return ::Namespace.where(id: user.forkable_namespaces).sort_by_type unless options[:only_groups] + + ::Group.where(id: user.manageable_groups(include_groups_with_developer_maintainer_access: true)) + end + # rubocop: enable CodeReuse/ActiveRecord end ForkTargetsFinder.prepend_mod_with('ForkTargetsFinder') diff --git a/app/graphql/resolvers/crm/contacts_resolver.rb b/app/graphql/resolvers/crm/contacts_resolver.rb index fc4019f36bb..a93942cf93b 100644 --- a/app/graphql/resolvers/crm/contacts_resolver.rb +++ b/app/graphql/resolvers/crm/contacts_resolver.rb @@ -11,7 +11,7 @@ module Resolvers type Types::CustomerRelations::ContactType, null: true argument :sort, Types::CustomerRelations::ContactSortEnum, - description: 'Criteria to sort issues by.', + description: 'Criteria to sort contacts by.', required: false, default_value: { field: 'last_name', direction: :asc } diff --git a/app/graphql/resolvers/projects/fork_targets_resolver.rb b/app/graphql/resolvers/projects/fork_targets_resolver.rb new file mode 100644 index 00000000000..5e8be325d43 --- /dev/null +++ b/app/graphql/resolvers/projects/fork_targets_resolver.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Resolvers + module Projects + class ForkTargetsResolver < BaseResolver + include ResolvesGroups + include Gitlab::Graphql::Authorize::AuthorizeResource + + type Types::NamespaceType.connection_type, null: true + + authorize :fork_project + authorizes_object! + + alias_method :project, :object + + argument :search, GraphQL::Types::String, + required: false, + description: 'Search query for path or name.' + + private + + def resolve_groups(**args) + ForkTargetsFinder.new(project, current_user).execute(args) + end + end + end +end diff --git a/app/graphql/types/customer_relations/contact_state_counts_type.rb b/app/graphql/types/customer_relations/contact_state_counts_type.rb index 5bee0b78db7..96230f8a952 100644 --- a/app/graphql/types/customer_relations/contact_state_counts_type.rb +++ b/app/graphql/types/customer_relations/contact_state_counts_type.rb @@ -4,7 +4,7 @@ module Types module CustomerRelations class ContactStateCountsType < Types::BaseObject graphql_name 'ContactStateCounts' - description 'Represents the total number of contacts for the represented statuses.' + description 'Represents the total number of contacts for the represented states.' authorize :read_crm_contact diff --git a/app/graphql/types/group_type.rb b/app/graphql/types/group_type.rb index 23f5e81a652..235a2bc2a34 100644 --- a/app/graphql/types/group_type.rb +++ b/app/graphql/types/group_type.rb @@ -220,7 +220,7 @@ module Types field :contact_state_counts, Types::CustomerRelations::ContactStateCountsType, null: true, - description: 'Counts of contacts by status for the group.', + description: 'Counts of contacts by state for the group.', resolver: Resolvers::Crm::ContactStateCountsResolver field :work_item_types, Types::WorkItems::TypeType.connection_type, diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index c9223577b07..668b3ec548b 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -444,6 +444,10 @@ module Types description: "Timelog categories for the project.", _deprecated_feature_flag: :timelog_categories + field :fork_targets, Types::NamespaceType.connection_type, + resolver: Resolvers::Projects::ForkTargetsResolver, + description: 'Namespaces in which the current user can fork the project into.' + def timelog_categories object.project_namespace.timelog_categories end diff --git a/app/models/customer_relations/contact_state_counts.rb b/app/models/customer_relations/contact_state_counts.rb index 33d8f918d3a..31c95e166bb 100644 --- a/app/models/customer_relations/contact_state_counts.rb +++ b/app/models/customer_relations/contact_state_counts.rb @@ -1,7 +1,6 @@ # frozen_string_literal: true module CustomerRelations - # Represents counts of each status or category of statuses class ContactStateCounts include Gitlab::Utils::StrongMemoize diff --git a/app/models/user.rb b/app/models/user.rb index f76e158b82b..64bc2d3febd 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1659,7 +1659,14 @@ class User < ApplicationRecord end def forkable_namespaces - @forkable_namespaces ||= [namespace] + manageable_groups(include_groups_with_developer_maintainer_access: true) + strong_memoize(:forkable_namespaces) do + personal_namespace = Namespace.where(id: namespace_id) + + Namespace.from_union([ + manageable_groups(include_groups_with_developer_maintainer_access: true), + personal_namespace + ]) + end end def manageable_groups(include_groups_with_developer_maintainer_access: false) diff --git a/config/feature_flags/development/searchable_fork_targets.yml b/config/feature_flags/development/searchable_fork_targets.yml new file mode 100644 index 00000000000..aeeeb66d2f8 --- /dev/null +++ b/config/feature_flags/development/searchable_fork_targets.yml @@ -0,0 +1,8 @@ +--- +name: searchable_fork_targets +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/94991 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/370795 +milestone: '15.3' +type: development +group: group::source code +default_enabled: false diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 63d87c83e65..4f6e050282c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -10560,7 +10560,7 @@ Connection details for an Agent. ### `ContactStateCounts` -Represents the total number of contacts for the represented statuses. +Represents the total number of contacts for the represented states. #### Fields @@ -12284,7 +12284,7 @@ four standard [pagination arguments](#connection-pagination-arguments): ##### `Group.contactStateCounts` -Counts of contacts by status for the group. +Counts of contacts by state for the group. Returns [`ContactStateCounts`](#contactstatecounts). @@ -12311,7 +12311,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | ---- | ---- | ----------- | | <a id="groupcontactsids"></a>`ids` | [`[CustomerRelationsContactID!]`](#customerrelationscontactid) | Filter contacts by IDs. | | <a id="groupcontactssearch"></a>`search` | [`String`](#string) | Search term to find contacts with. | -| <a id="groupcontactssort"></a>`sort` | [`ContactSort`](#contactsort) | Criteria to sort issues by. | +| <a id="groupcontactssort"></a>`sort` | [`ContactSort`](#contactsort) | Criteria to sort contacts by. | | <a id="groupcontactsstate"></a>`state` | [`CustomerRelationsContactState`](#customerrelationscontactstate) | State of the contacts to search for. | ##### `Group.containerRepositories` @@ -15818,6 +15818,22 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="projectenvironmentssearch"></a>`search` | [`String`](#string) | Search query for environment name. | | <a id="projectenvironmentsstates"></a>`states` | [`[String!]`](#string) | States of environments that should be included in result. | +##### `Project.forkTargets` + +Namespaces in which the current user can fork the project into. + +Returns [`NamespaceConnection`](#namespaceconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + +###### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="projectforktargetssearch"></a>`search` | [`String`](#string) | Search query for path or name. | + ##### `Project.incidentManagementEscalationPolicies` Incident Management escalation policies of the project. diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md index 635cb04e2e8..caecdff6e75 100644 --- a/doc/api/project_import_export.md +++ b/doc/api/project_import_export.md @@ -50,6 +50,11 @@ NOTE: The upload request is sent with `Content-Type: application/gzip` header. Ensure that your pre-signed URL includes this as part of the signature. NOTE: +The project export process may take some time to complete. Make sure the +upload URL doesn't have a short expiration time and is available thought +the export process. + +NOTE: As an administrator, you can modify the maximum export file size. By default, it is set to `0`, for unlimited. To change this value, edit `max_export_size` in the [Application settings API](settings.md#change-application-settings) diff --git a/doc/topics/autodevops/troubleshooting.md b/doc/topics/autodevops/troubleshooting.md index cee4ba68b49..045f843be44 100644 --- a/doc/topics/autodevops/troubleshooting.md +++ b/doc/topics/autodevops/troubleshooting.md @@ -100,7 +100,7 @@ WARNING: Setting `POSTGRES_ENABLED` to `false` permanently deletes any existing channel 1 database for your environment. -## Error: unable to recognize "": no matches for kind "Deployment" in version "extensions/v1beta1" +## `Error: unable to recognize "": no matches for kind "Deployment" in version "extensions/v1beta1"` After upgrading your Kubernetes cluster to [v1.16+](stages.md#kubernetes-116), you may encounter this message when deploying with Auto DevOps: @@ -148,7 +148,7 @@ that works for this problem. Follow these steps to use the tool in Auto DevOps: 1. Continue the deployments as usual. -## Error: error initializing: Looks like "https://kubernetes-charts.storage.googleapis.com" is not a valid chart repository or cannot be reached +## `Error: error initializing: Looks like "https://kubernetes-charts.storage.googleapis.com" is not a valid chart repository or cannot be reached` As [announced in the official CNCF blog post](https://www.cncf.io/blog/2020/10/07/important-reminder-for-all-helm-users-stable-incubator-repos-are-deprecated-and-all-images-are-changing-location/), the stable Helm chart repository was deprecated and removed on November 13th, 2020. @@ -197,7 +197,7 @@ To fix your custom chart: You can find more information in [issue #263778, "Migrate PostgreSQL from stable Helm repository"](https://gitlab.com/gitlab-org/gitlab/-/issues/263778). -## Error: release .... failed: timed out waiting for the condition +## `Error: release .... failed: timed out waiting for the condition` When getting started with Auto DevOps, you may encounter this error when first deploying your application: diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md index c04d10a6e93..2c011d3c940 100644 --- a/doc/user/application_security/api_fuzzing/index.md +++ b/doc/user/application_security/api_fuzzing/index.md @@ -1677,7 +1677,7 @@ For more information, see [Offline environments](../offline_deployments/index.md ## Troubleshooting -### Error waiting for API Security 'http://127.0.0.1:5000' to become available +### `Error waiting for API Security 'http://127.0.0.1:5000' to become available` A bug exists in versions of the API Fuzzing analyzer prior to v1.6.196 that can cause a background process to fail under certain conditions. The solution is to update to a newer version of the DAST API analyzer. @@ -1690,7 +1690,7 @@ If the issue is occurring with versions v1.6.196 or greater, please contact Supp 1. The `gl-api-security-scanner.log` file available as a job artifact. In the right-hand panel of the job details page, select the **Browse** button. 1. The `apifuzzer_fuzz` job definition from your `.gitlab-ci.yml` file. -### Error, the OpenAPI document is not valid. Errors were found during validation of the document using the published OpenAPI schema +### `Error, the OpenAPI document is not valid. Errors were found during validation of the document using the published OpenAPI schema` At the start of an API Fuzzing job the OpenAPI Specification is validated against the [published schema](https://github.com/OAI/OpenAPI-Specification/tree/master/schemas). This error is shown when the provided OpenAPI Specification has validation errors. Errors can be introduced when creating an OpenAPI Specification manually, and also when the schema is generated. @@ -1719,7 +1719,7 @@ For OpenAPI Specifications that are generated automatically validation errors ar 1. Alternatively, you can check the log output and look for schema validation warnings. They are prefixed with messages such as `OpenAPI 2.0 schema validation error` or `OpenAPI 3.0.x schema validation error`. Each failed validation provides extra information about `location` and `description`. Correct each of the validation failures and then resubmit the OpenAPI doc. Note that JSON Schema validation message might not be easy to understand. This is why we recommend the use of editors to validate document. 1. Once the validation issues are resolved, re-run your pipeline. -### Failed to start scanner session (version header not found) +### `Failed to start scanner session (version header not found)` The API Fuzzing engine outputs an error message when it cannot establish a connection with the scanner application component. The error message is shown in the job output window of the `apifuzzer_fuzz` job. A common cause of this issue is changing the `FUZZAPI_API` variable from its default. @@ -1733,7 +1733,7 @@ The API Fuzzing engine outputs an error message when it cannot establish a conne - Remove the `FUZZAPI_API` variable from the `.gitlab-ci.yml` file. The value will be inherited from the API Fuzzing CI/CD template. We recommend this method instead of manually setting a value. - If removing the variable is not possible, check to see if this value has changed in the latest version of the [API Fuzzing CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/API-Fuzzing.gitlab-ci.yml). If so, update the value in the `.gitlab-ci.yml` file. -### Application cannot determine the base URL for the target API +### `Application cannot determine the base URL for the target API` The API Fuzzing analyzer outputs an error message when it cannot determine the target API after inspecting the OpenAPI document. This error message is shown when the target API has not been set in the `.gitlab-ci.yml`file, it is not available in the `environment_url.txt` file, and it could not be computed using the OpenAPI document. @@ -1826,7 +1826,7 @@ API Security can still try to consume an OpenAPI document that does not fully co FUZZAPI_OPENAPI_RELAXED_VALIDATION: On ``` -### No operation in the OpenAPI document is consuming any supported media type +### `No operation in the OpenAPI document is consuming any supported media type` API Security uses the specified media types in the OpenAPI document to generate requests. If no request can be created due to the lack of supported media types, then an error will be thrown. diff --git a/doc/user/application_security/coverage_fuzzing/index.md b/doc/user/application_security/coverage_fuzzing/index.md index ac3b266ad48..154884c16e7 100644 --- a/doc/user/application_security/coverage_fuzzing/index.md +++ b/doc/user/application_security/coverage_fuzzing/index.md @@ -367,12 +367,12 @@ vulnerability: ## Troubleshooting -### Error "Unable to extract corpus folder from artifacts zip file" +### Error `Unable to extract corpus folder from artifacts zip file` If you see this error message, and `COVFUZZ_USE_REGISTRY` is set to `true`, ensure that the uploaded corpus file extracts into a folder named `corpus`. -### Error "400 Bad request - Duplicate package is not allowed" +### Error `400 Bad request - Duplicate package is not allowed` If you see this error message when running the fuzzing job with `COVFUZZ_USE_REGISTRY` set to `true`, ensure that duplicates are allowed. For more details, see diff --git a/doc/user/application_security/dast/index.md b/doc/user/application_security/dast/index.md index e745f12fe31..a49dd8fd646 100644 --- a/doc/user/application_security/dast/index.md +++ b/doc/user/application_security/dast/index.md @@ -351,7 +351,7 @@ include: - template: DAST-API.gitlab-ci.yml variables: - DAST_API_OPENAPI: http://my.api/api-specification.yml + DAST_API_SPECIFICATION: http://my.api/api-specification.yml ``` #### Import API specification from a file @@ -366,7 +366,7 @@ dast: - cp api-specification.yml /zap/wrk/api-specification.yml variables: GIT_STRATEGY: fetch - DAST_API_OPENAPI: api-specification.yml + DAST_API_SPECIFICATION: api-specification.yml ``` #### Full API scan @@ -402,7 +402,7 @@ include: - template: DAST-API.gitlab-ci.yml variables: - DAST_API_OPENAPI: http://api-test.host.com/api-specification.yml + DAST_API_SPECIFICATION: http://api-test.host.com/api-specification.yml DAST_API_HOST_OVERRIDE: api-test.host.com ``` @@ -417,7 +417,7 @@ include: - template: DAST-API.gitlab-ci.yml variables: - DAST_API_OPENAPI: http://api-test.api.com/api-specification.yml + DAST_API_SPECIFICATION: http://api-test.api.com/api-specification.yml DAST_REQUEST_HEADERS: "Authorization: Bearer my.token" ``` @@ -633,8 +633,7 @@ including a large number of false positives. | `DAST_ADVERTISE_SCAN` | boolean | Set to `true` to add a `Via` header to every request sent, advertising that the request was sent as part of a GitLab DAST scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/334947) in GitLab 14.1. | | `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. | | `DAST_API_HOST_OVERRIDE` <sup>1</sup> | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080`. | -| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. | -| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/290241)** in GitLab 15.0. Replaced by `DAST_API_OPENAPI`. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. | +| `DAST_API_SPECIFICATION` <sup>1</sup> | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. The variable `DAST_WEBSITE` must be specified if this is omitted. | | `DAST_AUTH_REPORT` <sup>2</sup> | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. | | `DAST_AUTH_EXCLUDE_URLS` <sup>2</sup> | URLs | **{warning}** **[Removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289959)** in GitLab 14.0. Replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. | | `DAST_AUTH_URL` <sup>1,2</sup> | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. Example: `https://login.example.com`. | @@ -671,7 +670,7 @@ including a large number of false positives. | `DAST_USERNAME` <sup>1,2</sup> | string | The username to authenticate to in the website. Example: `admin` | | `DAST_USERNAME_FIELD` <sup>1,2</sup> | string | The selector of username field at the sign-in HTML form. Example: `name:username` | | `DAST_XML_REPORT` | string | The filename of the XML report written at the end of a scan. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | -| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. The variable `DAST_API_OPENAPI` must be specified if this is omitted. | +| `DAST_WEBSITE` <sup>1</sup> | URL | The URL of the website to scan. The variable `DAST_API_SPECIFICATION` must be specified if this is omitted. | | `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | | `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. Example: `logger.httpsender.name=org.parosproxy.paros.network.HttpSender;logger.httpsender.level=debug;logger.sitemap.name=org.parosproxy.paros.model.SiteMap;logger.sitemap.level=debug;` | | `SECURE_ANALYZERS_PREFIX` | URL | Set the Docker registry base address from which to download the analyzer. | diff --git a/doc/user/application_security/dast_api/index.md b/doc/user/application_security/dast_api/index.md index 6bcc7b44b4e..319b5bd26b2 100644 --- a/doc/user/application_security/dast_api/index.md +++ b/doc/user/application_security/dast_api/index.md @@ -1524,7 +1524,7 @@ For more information, see [Offline environments](../offline_deployments/index.md ## Troubleshooting -### Error waiting for API Security 'http://127.0.0.1:5000' to become available +### `Error waiting for API Security 'http://127.0.0.1:5000' to become available` A bug exists in versions of the DAST API analyzer prior to v1.6.196 that can cause a background process to fail under certain conditions. The solution is to update to a newer version of the DAST API analyzer. @@ -1537,7 +1537,7 @@ If the issue is occurring with versions v1.6.196 or greater, please contact Supp 1. The `gl-api-security-scanner.log` file available as a job artifact. In the right-hand panel of the job details page, select the **Browse** button. 1. The `dast_api` job definition from your `.gitlab-ci.yml` file. -### Failed to start scanner session (version header not found) +### `Failed to start scanner session (version header not found)` The DAST API engine outputs an error message when it cannot establish a connection with the scanner application component. The error message is shown in the job output window of the `dast_api` job. A common cause of this issue is changing the `DAST_API_API` variable from its default. @@ -1551,7 +1551,7 @@ The DAST API engine outputs an error message when it cannot establish a connecti - Remove the `DAST_API_API` variable from the `.gitlab-ci.yml` file. The value will be inherited from the DAST API CI/CD template. We recommend this method instead of manually setting a value. - If removing the variable is not possible, check to see if this value has changed in the latest version of the [DAST API CI/CD template](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/Security/DAST-API.gitlab-ci.yml). If so, update the value in the `.gitlab-ci.yml` file. -### Application cannot determine the base URL for the target API +### `Application cannot determine the base URL for the target API` The DAST API engine outputs an error message when it cannot determine the target API after inspecting the OpenAPI document. This error message is shown when the target API has not been set in the `.gitlab-ci.yml` file, it is not available in the `environment_url.txt` file, and it could not be computed using the OpenAPI document. @@ -1636,7 +1636,7 @@ API Security can still try to consume an OpenAPI document that does not fully co DAST_API_OPENAPI_RELAXED_VALIDATION: On ``` -### No operation in the OpenAPI document is consuming any supported media type +### `No operation in the OpenAPI document is consuming any supported media type` API Security uses the specified media types in the OpenAPI document to generate requests. If no request can be created due to the lack of supported media types, then an error will be thrown. diff --git a/doc/user/application_security/policies/index.md b/doc/user/application_security/policies/index.md index 5eafeb7bd79..53f9c400259 100644 --- a/doc/user/application_security/policies/index.md +++ b/doc/user/application_security/policies/index.md @@ -140,7 +140,7 @@ for more information on the product direction of security policies within GitLab ## Troubleshooting -### Branch name does not follow the pattern `'update-policy-<timestamp>'` +### `Branch name does not follow the pattern 'update-policy-<timestamp>'` When you create a new security policy or change an existing policy, a new branch is automatically created with the branch name following the pattern `update-policy-<timestamp>`. For example: `update-policy-1659094451`. diff --git a/qa/qa/specs/features/api/5_package/container_registry_spec.rb b/qa/qa/specs/features/api/5_package/container_registry_spec.rb index a9d7562d464..8412c0b2872 100644 --- a/qa/qa/specs/features/api/5_package/container_registry_spec.rb +++ b/qa/qa/specs/features/api/5_package/container_registry_spec.rb @@ -3,7 +3,7 @@ require 'airborne' module QA - RSpec.describe 'Package', :reliable, only: { subdomain: %i[staging pre] } do + RSpec.describe 'Package', :reliable, only: { subdomain: %i[staging staging-canary pre] } do include Support::API include Support::Helpers::MaskToken @@ -41,7 +41,7 @@ module QA stages: - build - test - + build: image: docker:19.03.12 stage: build @@ -60,7 +60,7 @@ module QA - docker build -t $IMAGE_TAG . - docker push $IMAGE_TAG - docker pull $IMAGE_TAG - + test: image: dwdraju/alpine-curl-jq:latest stage: test @@ -72,7 +72,7 @@ module QA - 'status_code=$(curl --request DELETE --head --output /dev/null --write-out "%{http_code}\n" --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories/$id/tags/master")' - if [ $status_code -ne 200 ]; then exit 1; fi; - 'status_code=$(curl --head --output /dev/null --write-out "%{http_code}\n" --header "PRIVATE-TOKEN: #{masked_token}" "https://${CI_SERVER_HOST}/api/v4/projects/#{project.id}/registry/repositories/$id/tags/master")' - - if [ $status_code -ne 404 ]; then exit 1; fi; + - if [ $status_code -ne 404 ]; then exit 1; fi; YAML end diff --git a/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb index 2c7656e20f1..1d30b915594 100644 --- a/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb +++ b/qa/qa/specs/features/browser_ui/1_manage/login/maintain_log_in_mixed_env_spec.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true module QA - RSpec.describe 'Manage', only: { subdomain: :staging }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/344213', type: :stale } do + RSpec.describe 'Manage', only: { subdomain: %i[staging staging-canary] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/344213', type: :stale } do describe 'basic user' do it 'remains logged in when redirected from canary to non-canary node', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347626' do Runtime::Browser.visit(:gitlab, Page::Main::Login) diff --git a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb index bfbb21956a8..5a29b44e8b3 100644 --- a/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb +++ b/qa/qa/specs/features/browser_ui/5_package/container_registry/container_registry_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Package' do - describe 'Container Registry', only: { subdomain: %i[staging pre] } do + describe 'Container Registry', only: { subdomain: %i[staging staging-canary pre] } do let(:project) do Resource::Project.fabricate_via_api! do |project| project.name = 'project-with-registry' diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb index 980c6da2576..608dd7e089f 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/auto_devops_templates_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Configure' do - describe 'AutoDevOps Templates', only: { subdomain: :staging } do + describe 'AutoDevOps Templates', only: { subdomain: %i[staging staging-canary] } do using RSpec::Parameterized::TableSyntax # specify jobs to be disabled in the pipeline. diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index dca6f961047..f1a2eb71390 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -2,7 +2,7 @@ module QA RSpec.describe 'Configure', - only: { subdomain: :staging }, + only: { subdomain: %i[staging staging-canary] }, quarantine: { issue: 'https://gitlab.com/gitlab-org/quality/team-tasks/-/issues/1198', type: :waiting_on diff --git a/spec/finders/fork_targets_finder_spec.rb b/spec/finders/fork_targets_finder_spec.rb index fe5b50ef030..1acc38bb492 100644 --- a/spec/finders/fork_targets_finder_spec.rb +++ b/spec/finders/fork_targets_finder_spec.rb @@ -5,27 +5,27 @@ require 'spec_helper' RSpec.describe ForkTargetsFinder do subject(:finder) { described_class.new(project, user) } - let(:project) { create(:project, namespace: create(:group)) } - let(:user) { create(:user) } - let!(:maintained_group) do + let_it_be(:project) { create(:project, namespace: create(:group)) } + let_it_be(:user) { create(:user) } + let_it_be(:maintained_group) do create(:group).tap { |g| g.add_maintainer(user) } end - let!(:owned_group) do + let_it_be(:owned_group) do create(:group).tap { |g| g.add_owner(user) } end - let!(:developer_group) do + let_it_be(:developer_group) do create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS).tap do |g| g.add_developer(user) end end - let!(:reporter_group) do + let_it_be(:reporter_group) do create(:group).tap { |g| g.add_reporter(user) } end - let!(:guest_group) do + let_it_be(:guest_group) do create(:group).tap { |g| g.add_guest(user) } end @@ -33,7 +33,7 @@ RSpec.describe ForkTargetsFinder do project.namespace.add_owner(user) end - describe '#execute' do + shared_examples 'returns namespaces and groups' do it 'returns all user manageable namespaces' do expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group, project.namespace, developer_group]) end @@ -46,4 +46,28 @@ RSpec.describe ForkTargetsFinder do expect(finder.execute(only_groups: true)).to include(a_kind_of(Group)) end end + + describe '#execute' do + it_behaves_like 'returns namespaces and groups' + + context 'when search is provided' do + it 'filters the targets by the param' do + expect(finder.execute(search: maintained_group.path)).to eq([maintained_group]) + end + end + + context 'when searchable_fork_targets feature flag is disabled' do + before do + stub_feature_flags(searchable_fork_targets: false) + end + + it_behaves_like 'returns namespaces and groups' + + context 'when search is provided' do + it 'ignores the param and returns all user manageable namespaces' do + expect(finder.execute).to match_array([user.namespace, maintained_group, owned_group, project.namespace, developer_group]) + end + end + end + end end diff --git a/spec/frontend/environments/canary_ingress_spec.js b/spec/frontend/environments/canary_ingress_spec.js index d58f9f9b8a2..340740e6499 100644 --- a/spec/frontend/environments/canary_ingress_spec.js +++ b/spec/frontend/environments/canary_ingress_spec.js @@ -10,7 +10,7 @@ describe('/environments/components/canary_ingress.vue', () => { const setWeightTo = (weightWrapper, x) => weightWrapper - .findAll(GlDropdownItem) + .findAllComponents(GlDropdownItem) .at(x / 5) .vm.$emit('click'); @@ -59,14 +59,14 @@ describe('/environments/components/canary_ingress.vue', () => { }); it('lists options from 0 to 100 in increments of 5', () => { - const options = stableWeightDropdown.findAll(GlDropdownItem); + const options = stableWeightDropdown.findAllComponents(GlDropdownItem); expect(options).toHaveLength(21); options.wrappers.forEach((w, i) => expect(w.text()).toBe((i * 5).toString())); }); it('is set to open the change modal', () => { stableWeightDropdown - .findAll(GlDropdownItem) + .findAllComponents(GlDropdownItem) .wrappers.forEach((w) => expect(getBinding(w.element, 'gl-modal')).toMatchObject({ value: CANARY_UPDATE_MODAL }), ); @@ -92,13 +92,13 @@ describe('/environments/components/canary_ingress.vue', () => { it('lists options from 0 to 100 in increments of 5', () => { canaryWeightDropdown - .findAll(GlDropdownItem) + .findAllComponents(GlDropdownItem) .wrappers.forEach((w, i) => expect(w.text()).toBe((i * 5).toString())); }); it('is set to open the change modal', () => { canaryWeightDropdown - .findAll(GlDropdownItem) + .findAllComponents(GlDropdownItem) .wrappers.forEach((w) => expect(getBinding(w.element, 'gl-modal')).toMatchObject({ value: CANARY_UPDATE_MODAL }), ); diff --git a/spec/frontend/environments/canary_update_modal_spec.js b/spec/frontend/environments/canary_update_modal_spec.js index 16792dcda1e..31b1770da59 100644 --- a/spec/frontend/environments/canary_update_modal_spec.js +++ b/spec/frontend/environments/canary_update_modal_spec.js @@ -10,7 +10,7 @@ describe('/environments/components/canary_update_modal.vue', () => { let modal; let mutate; - const findAlert = () => wrapper.find(GlAlert); + const findAlert = () => wrapper.findComponent(GlAlert); const createComponent = () => { mutate = jest.fn().mockResolvedValue(); @@ -27,7 +27,7 @@ describe('/environments/components/canary_update_modal.vue', () => { $apollo: { mutate }, }, }); - modal = wrapper.find(GlModal); + modal = wrapper.findComponent(GlModal); }; afterEach(() => { diff --git a/spec/frontend/environments/confirm_rollback_modal_spec.js b/spec/frontend/environments/confirm_rollback_modal_spec.js index c4763933468..2163814528a 100644 --- a/spec/frontend/environments/confirm_rollback_modal_spec.js +++ b/spec/frontend/environments/confirm_rollback_modal_spec.js @@ -73,7 +73,7 @@ describe('Confirm Rollback Modal Component', () => { hasMultipleCommits, retryUrl, }); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); expect(modal.attributes('title')).toContain('Rollback'); expect(modal.attributes('title')).toContain('test'); @@ -92,7 +92,7 @@ describe('Confirm Rollback Modal Component', () => { hasMultipleCommits, }); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); expect(modal.attributes('title')).toContain('Re-deploy'); expect(modal.attributes('title')).toContain('test'); @@ -110,7 +110,7 @@ describe('Confirm Rollback Modal Component', () => { }); const eventHubSpy = jest.spyOn(eventHub, '$emit'); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); modal.vm.$emit('ok'); expect(eventHubSpy).toHaveBeenCalledWith('rollbackEnvironment', env); @@ -155,7 +155,7 @@ describe('Confirm Rollback Modal Component', () => { }, { apolloProvider }, ); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); expect(trimText(modal.text())).toContain('commit abc0123'); expect(modal.text()).toContain('Are you sure you want to continue?'); @@ -177,7 +177,7 @@ describe('Confirm Rollback Modal Component', () => { }, { apolloProvider }, ); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); expect(modal.attributes('title')).toContain('Rollback'); expect(modal.attributes('title')).toContain('test'); @@ -201,7 +201,7 @@ describe('Confirm Rollback Modal Component', () => { { apolloProvider }, ); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); expect(modal.attributes('title')).toContain('Re-deploy'); expect(modal.attributes('title')).toContain('test'); @@ -220,7 +220,7 @@ describe('Confirm Rollback Modal Component', () => { { apolloProvider }, ); - const modal = component.find(GlModal); + const modal = component.findComponent(GlModal); modal.vm.$emit('ok'); await nextTick(); diff --git a/spec/frontend/environments/deploy_board_component_spec.js b/spec/frontend/environments/deploy_board_component_spec.js index 4d63648dd48..c005ca22070 100644 --- a/spec/frontend/environments/deploy_board_component_spec.js +++ b/spec/frontend/environments/deploy_board_component_spec.js @@ -26,7 +26,9 @@ describe('Deploy Board', () => { }); it('should render percentage with completion value provided', () => { - expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${deployBoardMockData.completion}%`); + expect(wrapper.findComponent({ ref: 'percentage' }).text()).toBe( + `${deployBoardMockData.completion}%`, + ); }); it('should render total instance count', () => { @@ -79,7 +81,9 @@ describe('Deploy Board', () => { }); it('should render percentage with completion value provided', () => { - expect(wrapper.find({ ref: 'percentage' }).text()).toBe(`${rolloutStatus.completion}%`); + expect(wrapper.findComponent({ ref: 'percentage' }).text()).toBe( + `${rolloutStatus.completion}%`, + ); }); it('should render total instance count', () => { diff --git a/spec/frontend/environments/edit_environment_spec.js b/spec/frontend/environments/edit_environment_spec.js index 2c8c054ccbd..0f2d6e95bf0 100644 --- a/spec/frontend/environments/edit_environment_spec.js +++ b/spec/frontend/environments/edit_environment_spec.js @@ -42,7 +42,7 @@ describe('~/environments/components/edit.vue', () => { const findExternalUrlInput = () => wrapper.findByLabelText('External URL'); const findForm = () => wrapper.findByRole('form', { name: 'Edit environment' }); - const showsLoading = () => wrapper.find(GlLoadingIcon).exists(); + const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists(); const submitForm = async (expected, response) => { mock diff --git a/spec/frontend/environments/environment_actions_spec.js b/spec/frontend/environments/environment_actions_spec.js index ada79e2d415..68895b194a1 100644 --- a/spec/frontend/environments/environment_actions_spec.js +++ b/spec/frontend/environments/environment_actions_spec.js @@ -51,7 +51,7 @@ describe('EnvironmentActions Component', () => { } const findDropdownItem = (action) => { - const buttons = wrapper.findAll(GlDropdownItem); + const buttons = wrapper.findAllComponents(GlDropdownItem); return buttons.filter((button) => button.text().startsWith(action.name)).at(0); }; @@ -62,12 +62,12 @@ describe('EnvironmentActions Component', () => { it('should render a dropdown button with 2 icons', () => { createComponent({}, { mountFn: mount }); - expect(wrapper.find(GlDropdown).findAll(GlIcon).length).toBe(2); + expect(wrapper.findComponent(GlDropdown).findAllComponents(GlIcon).length).toBe(2); }); it('should render a dropdown button with aria-label description', () => { createComponent(); - expect(wrapper.find(GlDropdown).attributes('aria-label')).toBe('Deploy to...'); + expect(wrapper.findComponent(GlDropdown).attributes('aria-label')).toBe('Deploy to...'); }); it('should render a tooltip', () => { @@ -98,11 +98,11 @@ describe('EnvironmentActions Component', () => { }); it('should render a dropdown with the provided list of actions', () => { - expect(wrapper.findAll(GlDropdownItem)).toHaveLength(actions.length); + expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(actions.length); }); it("should render a disabled action when it's not playable", () => { - const dropdownItems = wrapper.findAll(GlDropdownItem); + const dropdownItems = wrapper.findAllComponents(GlDropdownItem); const lastDropdownItem = dropdownItems.at(dropdownItems.length - 1); expect(lastDropdownItem.attributes('disabled')).toBe('true'); }); @@ -136,7 +136,7 @@ describe('EnvironmentActions Component', () => { }); it('should render a dropdown button with a loading icon', () => { - expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).isVisible()).toBe(true); }); }); diff --git a/spec/frontend/environments/environment_delete_spec.js b/spec/frontend/environments/environment_delete_spec.js index 057cb9858c4..530f9f55088 100644 --- a/spec/frontend/environments/environment_delete_spec.js +++ b/spec/frontend/environments/environment_delete_spec.js @@ -21,7 +21,7 @@ describe('External URL Component', () => { }); }; - const findDropdownItem = () => wrapper.find(GlDropdownItem); + const findDropdownItem = () => wrapper.findComponent(GlDropdownItem); describe('event hub', () => { beforeEach(() => { diff --git a/spec/frontend/environments/environment_pin_spec.js b/spec/frontend/environments/environment_pin_spec.js index 669c974ea4f..170036b5b00 100644 --- a/spec/frontend/environments/environment_pin_spec.js +++ b/spec/frontend/environments/environment_pin_spec.js @@ -41,7 +41,7 @@ describe('Pin Component', () => { it('should emit onPinClick when clicked', () => { const eventHubSpy = jest.spyOn(eventHub, '$emit'); - const item = wrapper.find(GlDropdownItem); + const item = wrapper.findComponent(GlDropdownItem); item.vm.$emit('click'); @@ -74,7 +74,7 @@ describe('Pin Component', () => { it('should emit onPinClick when clicked', () => { jest.spyOn(mockApollo.defaultClient, 'mutate'); - const item = wrapper.find(GlDropdownItem); + const item = wrapper.findComponent(GlDropdownItem); item.vm.$emit('click'); diff --git a/spec/frontend/environments/environment_rollback_spec.js b/spec/frontend/environments/environment_rollback_spec.js index 7eff46baaf7..be61c6fcc90 100644 --- a/spec/frontend/environments/environment_rollback_spec.js +++ b/spec/frontend/environments/environment_rollback_spec.js @@ -44,7 +44,7 @@ describe('Rollback Component', () => { }, }, }); - const button = wrapper.find(GlDropdownItem); + const button = wrapper.findComponent(GlDropdownItem); button.vm.$emit('click'); @@ -71,7 +71,7 @@ describe('Rollback Component', () => { }, apolloProvider, }); - const button = wrapper.find(GlDropdownItem); + const button = wrapper.findComponent(GlDropdownItem); button.vm.$emit('click'); expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith({ diff --git a/spec/frontend/environments/environment_stop_spec.js b/spec/frontend/environments/environment_stop_spec.js index 358abca2f77..851e24c22cc 100644 --- a/spec/frontend/environments/environment_stop_spec.js +++ b/spec/frontend/environments/environment_stop_spec.js @@ -22,7 +22,7 @@ describe('Stop Component', () => { }); }; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); describe('eventHub', () => { beforeEach(() => { diff --git a/spec/frontend/environments/environment_table_spec.js b/spec/frontend/environments/environment_table_spec.js index aff6b1327f0..49a643aaac8 100644 --- a/spec/frontend/environments/environment_table_spec.js +++ b/spec/frontend/environments/environment_table_spec.js @@ -177,10 +177,10 @@ describe('Environment table', () => { }, }); - wrapper.find(DeployBoard).vm.$emit('changeCanaryWeight', 40); + wrapper.findComponent(DeployBoard).vm.$emit('changeCanaryWeight', 40); await nextTick(); - expect(wrapper.find(CanaryUpdateModal).props()).toMatchObject({ + expect(wrapper.findComponent(CanaryUpdateModal).props()).toMatchObject({ weight: 40, environment: mockItem, }); diff --git a/spec/frontend/environments/folder/environments_folder_view_spec.js b/spec/frontend/environments/folder/environments_folder_view_spec.js index 9eb57b2682f..f8b8465cf6f 100644 --- a/spec/frontend/environments/folder/environments_folder_view_spec.js +++ b/spec/frontend/environments/folder/environments_folder_view_spec.js @@ -65,7 +65,7 @@ describe('Environments Folder View', () => { }); it('should render a table with environments', () => { - const table = wrapper.find(EnvironmentTable); + const table = wrapper.findComponent(EnvironmentTable); expect(table.exists()).toBe(true); expect(table.find('.environment-name').text()).toEqual(environmentsList[0].name); @@ -93,7 +93,7 @@ describe('Environments Folder View', () => { describe('pagination', () => { it('should render pagination', () => { - expect(wrapper.find(GlPagination).exists()).toBe(true); + expect(wrapper.findComponent(GlPagination).exists()).toBe(true); }); it('should make an API request when changing page', () => { @@ -126,7 +126,7 @@ describe('Environments Folder View', () => { }); it('should not render a table', () => { - expect(wrapper.find(EnvironmentTable).exists()).toBe(false); + expect(wrapper.findComponent(EnvironmentTable).exists()).toBe(false); }); it('should render available tab with count 0', () => { diff --git a/spec/frontend/environments/new_environment_spec.js b/spec/frontend/environments/new_environment_spec.js index f6d970e02d8..5a1c1c7714c 100644 --- a/spec/frontend/environments/new_environment_spec.js +++ b/spec/frontend/environments/new_environment_spec.js @@ -40,7 +40,7 @@ describe('~/environments/components/new.vue', () => { wrapper.destroy(); }); - const showsLoading = () => wrapper.find(GlLoadingIcon).exists(); + const showsLoading = () => wrapper.findComponent(GlLoadingIcon).exists(); const submitForm = async (expected, response) => { mock diff --git a/spec/frontend/error_tracking/components/error_details_spec.js b/spec/frontend/error_tracking/components/error_details_spec.js index 4273da6c735..732eff65495 100644 --- a/spec/frontend/error_tracking/components/error_details_spec.js +++ b/spec/frontend/error_tracking/components/error_details_spec.js @@ -35,7 +35,9 @@ describe('ErrorDetails', () => { const externalUrl = 'https://sentry.io/organizations/test-sentry-nk/issues/1/?project=1'; const findInput = (name) => { - const inputs = wrapper.findAll(GlFormInput).filter((c) => c.attributes('name') === name); + const inputs = wrapper + .findAllComponents(GlFormInput) + .filter((c) => c.attributes('name') === name); return inputs.length ? inputs.at(0) : inputs; }; @@ -44,7 +46,7 @@ describe('ErrorDetails', () => { const findUpdateResolveStatusButton = () => wrapper.find('[data-testid="update-resolve-status-btn"]'); const findExternalUrl = () => wrapper.find('[data-testid="external-url-link"]'); - const findAlert = () => wrapper.find(GlAlert); + const findAlert = () => wrapper.findComponent(GlAlert); function mountComponent() { wrapper = shallowMount(ErrorDetails, { @@ -119,9 +121,9 @@ describe('ErrorDetails', () => { }); it('should show spinner while loading', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - expect(wrapper.find(GlLink).exists()).toBe(false); - expect(wrapper.find(Stacktrace).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLink).exists()).toBe(false); + expect(wrapper.findComponent(Stacktrace).exists()).toBe(false); }); }); @@ -141,7 +143,7 @@ describe('ErrorDetails', () => { wrapper.vm.onNoApolloResult(); await nextTick(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); expect(createFlash).not.toHaveBeenCalled(); expect(mocks.$apollo.queries.error.stopPolling).not.toHaveBeenCalled(); }); @@ -152,8 +154,8 @@ describe('ErrorDetails', () => { wrapper.vm.onNoApolloResult(); await nextTick(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(GlLink).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLink).exists()).toBe(false); expect(createFlash).toHaveBeenCalledWith({ message: 'Could not connect to Sentry. Refresh the page to try again.', type: 'warning', @@ -186,11 +188,11 @@ describe('ErrorDetails', () => { }); it('should show Sentry error details without stacktrace', () => { - expect(wrapper.find(GlLink).exists()).toBe(true); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - expect(wrapper.find(Stacktrace).exists()).toBe(false); - expect(wrapper.find(GlBadge).exists()).toBe(false); - expect(wrapper.findAll(GlButton)).toHaveLength(3); + expect(wrapper.findComponent(GlLink).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(Stacktrace).exists()).toBe(false); + expect(wrapper.findComponent(GlBadge).exists()).toBe(false); + expect(wrapper.findAllComponents(GlButton)).toHaveLength(3); }); describe('unsafe chars for culprit field', () => { @@ -227,7 +229,7 @@ describe('ErrorDetails', () => { }, }); await nextTick(); - expect(wrapper.findAll(GlBadge).length).toBe(2); + expect(wrapper.findAllComponents(GlBadge).length).toBe(2); }); it('should NOT show the badge if the tag is not present', async () => { @@ -239,7 +241,7 @@ describe('ErrorDetails', () => { }, }); await nextTick(); - expect(wrapper.findAll(GlBadge).length).toBe(1); + expect(wrapper.findAllComponents(GlBadge).length).toBe(1); }); it.each(Object.keys(severityLevel))( @@ -253,7 +255,7 @@ describe('ErrorDetails', () => { }, }); await nextTick(); - expect(wrapper.find(GlBadge).props('variant')).toEqual( + expect(wrapper.findComponent(GlBadge).props('variant')).toEqual( severityLevelVariant[severityLevel[level]], ); }, @@ -268,7 +270,7 @@ describe('ErrorDetails', () => { }, }); await nextTick(); - expect(wrapper.find(GlBadge).props('variant')).toEqual( + expect(wrapper.findComponent(GlBadge).props('variant')).toEqual( severityLevelVariant[severityLevel.ERROR], ); }); @@ -278,8 +280,8 @@ describe('ErrorDetails', () => { it('should show stacktrace', async () => { store.state.details.loadingStacktrace = false; await nextTick(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(Stacktrace).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(Stacktrace).exists()).toBe(true); expect(findAlert().exists()).toBe(false); }); @@ -287,8 +289,8 @@ describe('ErrorDetails', () => { store.state.details.loadingStacktrace = false; store.getters = { 'details/sentryUrl': () => 'sentry.io', 'details/stacktrace': () => [] }; await nextTick(); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(Stacktrace).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(Stacktrace).exists()).toBe(false); expect(findAlert().text()).toBe('No stack trace for this error'); }); }); diff --git a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js index 7ed4e5f6b05..5f6c9ddb4d7 100644 --- a/spec/frontend/error_tracking/components/error_tracking_actions_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_actions_spec.js @@ -35,7 +35,7 @@ describe('Error Tracking Actions', () => { } }); - const findButtons = () => wrapper.findAll(GlButton); + const findButtons = () => wrapper.findAllComponents(GlButton); describe('when error status is unresolved', () => { it('renders the correct actions buttons to allow ignore and resolve', async () => { diff --git a/spec/frontend/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js index 23d448f3964..b7dffbbec04 100644 --- a/spec/frontend/error_tracking/components/error_tracking_list_spec.js +++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js @@ -19,13 +19,13 @@ describe('ErrorTrackingList', () => { const findErrorListTable = () => wrapper.find('table'); const findErrorListRows = () => wrapper.findAll('tbody tr'); - const dropdownsArray = () => wrapper.findAll(GlDropdown); - const findRecentSearchesDropdown = () => dropdownsArray().at(0).find(GlDropdown); - const findStatusFilterDropdown = () => dropdownsArray().at(1).find(GlDropdown); - const findSortDropdown = () => dropdownsArray().at(2).find(GlDropdown); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findPagination = () => wrapper.find(GlPagination); - const findErrorActions = () => wrapper.find(ErrorTrackingActions); + const dropdownsArray = () => wrapper.findAllComponents(GlDropdown); + const findRecentSearchesDropdown = () => dropdownsArray().at(0).findComponent(GlDropdown); + const findStatusFilterDropdown = () => dropdownsArray().at(1).findComponent(GlDropdown); + const findSortDropdown = () => dropdownsArray().at(2).findComponent(GlDropdown); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findPagination = () => wrapper.findComponent(GlPagination); + const findErrorActions = () => wrapper.findComponent(ErrorTrackingActions); const findIntegratedDisabledAlert = () => wrapper.findByTestId('integrated-disabled-alert'); function mountComponent({ @@ -152,12 +152,12 @@ describe('ErrorTrackingList', () => { it('each error in the list should have an action button set', () => { findErrorListRows().wrappers.forEach((row) => { - expect(row.find(ErrorTrackingActions).exists()).toBe(true); + expect(row.findComponent(ErrorTrackingActions).exists()).toBe(true); }); }); describe('filtering', () => { - const findSearchBox = () => wrapper.find(GlFormInput); + const findSearchBox = () => wrapper.findComponent(GlFormInput); it('shows search box & sort dropdown', () => { expect(findSearchBox().exists()).toBe(true); @@ -222,7 +222,7 @@ describe('ErrorTrackingList', () => { }); it('shows empty state', () => { - expect(wrapper.find(GlEmptyState).exists()).toBe(true); + expect(wrapper.findComponent(GlEmptyState).exists()).toBe(true); expect(findLoadingIcon().exists()).toBe(false); expect(findErrorListTable().exists()).toBe(false); expect(dropdownsArray().length).toBe(0); @@ -327,7 +327,7 @@ describe('ErrorTrackingList', () => { }); it('shows empty state', () => { - expect(wrapper.find(GlEmptyState).isVisible()).toBe(true); + expect(wrapper.findComponent(GlEmptyState).isVisible()).toBe(true); }); }); @@ -358,7 +358,7 @@ describe('ErrorTrackingList', () => { }); describe('clear', () => { - const clearRecentButton = () => wrapper.find({ ref: 'clearRecentSearches' }); + const clearRecentButton = () => wrapper.findComponent({ ref: 'clearRecentSearches' }); it('is hidden when list empty', () => { store.state.list.recentSearches = []; diff --git a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js index 0b43167c19b..693fcff50ca 100644 --- a/spec/frontend/error_tracking/components/stacktrace_entry_spec.js +++ b/spec/frontend/error_tracking/components/stacktrace_entry_spec.js @@ -36,10 +36,10 @@ describe('Stacktrace Entry', () => { it('should render stacktrace entry collapsed', () => { mountComponent({ lines }); - expect(wrapper.find(StackTraceEntry).exists()).toBe(true); - expect(wrapper.find(ClipboardButton).exists()).toBe(true); - expect(wrapper.find(GlIcon).exists()).toBe(true); - expect(wrapper.find(FileIcon).exists()).toBe(true); + expect(wrapper.findComponent(StackTraceEntry).exists()).toBe(true); + expect(wrapper.findComponent(ClipboardButton).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).exists()).toBe(true); + expect(wrapper.findComponent(FileIcon).exists()).toBe(true); expect(wrapper.find('table').exists()).toBe(false); }); @@ -56,7 +56,7 @@ describe('Stacktrace Entry', () => { it('should hide collapse icon and render error fn name and error line when there is no code block', () => { const extraInfo = { errorLine: 34, errorFn: 'errorFn', errorColumn: 77 }; mountComponent({ expanded: false, lines: [], ...extraInfo }); - expect(wrapper.find(GlIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlIcon).exists()).toBe(false); expect(trimText(findFileHeaderContent())).toContain( `in ${extraInfo.errorFn} at line ${extraInfo.errorLine}:${extraInfo.errorColumn}`, ); diff --git a/spec/frontend/error_tracking/components/stacktrace_spec.js b/spec/frontend/error_tracking/components/stacktrace_spec.js index 4f4a60acba4..cd5a57f5683 100644 --- a/spec/frontend/error_tracking/components/stacktrace_spec.js +++ b/spec/frontend/error_tracking/components/stacktrace_spec.js @@ -33,13 +33,13 @@ describe('ErrorDetails', () => { it('should render single Stacktrace entry', () => { mountComponent([stackTraceEntry]); - expect(wrapper.findAll(StackTraceEntry).length).toBe(1); + expect(wrapper.findAllComponents(StackTraceEntry).length).toBe(1); }); it('should render multiple Stacktrace entry', () => { const entriesNum = 3; mountComponent(new Array(entriesNum).fill(stackTraceEntry)); - expect(wrapper.findAll(StackTraceEntry).length).toBe(entriesNum); + expect(wrapper.findAllComponents(StackTraceEntry).length).toBe(entriesNum); }); }); }); diff --git a/spec/frontend/monitoring/components/charts/anomaly_spec.js b/spec/frontend/monitoring/components/charts/anomaly_spec.js index 8dc6132709e..3674a49f42c 100644 --- a/spec/frontend/monitoring/components/charts/anomaly_spec.js +++ b/spec/frontend/monitoring/components/charts/anomaly_spec.js @@ -20,7 +20,7 @@ describe('Anomaly chart component', () => { propsData: { ...props }, }); }; - const findTimeSeries = () => wrapper.find(MonitorTimeSeriesChart); + const findTimeSeries = () => wrapper.findComponent(MonitorTimeSeriesChart); const getTimeSeriesProps = () => findTimeSeries().props(); describe('wrapped monitor-time-series-chart component', () => { diff --git a/spec/frontend/monitoring/components/charts/bar_spec.js b/spec/frontend/monitoring/components/charts/bar_spec.js index 6368c53943a..5339a7a525b 100644 --- a/spec/frontend/monitoring/components/charts/bar_spec.js +++ b/spec/frontend/monitoring/components/charts/bar_spec.js @@ -33,7 +33,7 @@ describe('Bar component', () => { let chartData; beforeEach(() => { - glbarChart = barChart.find(GlBarChart); + glbarChart = barChart.findComponent(GlBarChart); chartData = barChart.vm.chartData[graphData.metrics[0].label]; }); diff --git a/spec/frontend/monitoring/components/charts/column_spec.js b/spec/frontend/monitoring/components/charts/column_spec.js index e10cb3a456a..0158966997f 100644 --- a/spec/frontend/monitoring/components/charts/column_spec.js +++ b/spec/frontend/monitoring/components/charts/column_spec.js @@ -44,7 +44,7 @@ describe('Column component', () => { }, }); }; - const findChart = () => wrapper.find(GlColumnChart); + const findChart = () => wrapper.findComponent(GlColumnChart); const chartProps = (prop) => findChart().props(prop); beforeEach(() => { diff --git a/spec/frontend/monitoring/components/charts/gauge_spec.js b/spec/frontend/monitoring/components/charts/gauge_spec.js index c8f67d5d8c7..484199698ea 100644 --- a/spec/frontend/monitoring/components/charts/gauge_spec.js +++ b/spec/frontend/monitoring/components/charts/gauge_spec.js @@ -8,7 +8,7 @@ describe('Gauge Chart component', () => { let wrapper; - const findGaugeChart = () => wrapper.find(GlGaugeChart); + const findGaugeChart = () => wrapper.findComponent(GlGaugeChart); const createWrapper = ({ ...graphProps } = {}) => { wrapper = shallowMount(GaugeChart, { diff --git a/spec/frontend/monitoring/components/charts/heatmap_spec.js b/spec/frontend/monitoring/components/charts/heatmap_spec.js index 841b7e0648a..e163d4e73a0 100644 --- a/spec/frontend/monitoring/components/charts/heatmap_spec.js +++ b/spec/frontend/monitoring/components/charts/heatmap_spec.js @@ -8,7 +8,7 @@ describe('Heatmap component', () => { let wrapper; let store; - const findChart = () => wrapper.find(GlHeatmap); + const findChart = () => wrapper.findComponent(GlHeatmap); const graphData = heatmapGraphData(); diff --git a/spec/frontend/monitoring/components/charts/single_stat_spec.js b/spec/frontend/monitoring/components/charts/single_stat_spec.js index 8633b49565f..62a0b7e6ad3 100644 --- a/spec/frontend/monitoring/components/charts/single_stat_spec.js +++ b/spec/frontend/monitoring/components/charts/single_stat_spec.js @@ -15,7 +15,7 @@ describe('Single Stat Chart component', () => { }); }; - const findChart = () => wrapper.find(GlSingleStat); + const findChart = () => wrapper.findComponent(GlSingleStat); beforeEach(() => { createComponent(); diff --git a/spec/frontend/monitoring/components/charts/stacked_column_spec.js b/spec/frontend/monitoring/components/charts/stacked_column_spec.js index 9cab3650f28..91fe36bc6e4 100644 --- a/spec/frontend/monitoring/components/charts/stacked_column_spec.js +++ b/spec/frontend/monitoring/components/charts/stacked_column_spec.js @@ -15,8 +15,8 @@ describe('Stacked column chart component', () => { let wrapper; - const findChart = () => wrapper.find(GlStackedColumnChart); - const findLegend = () => wrapper.find(GlChartLegend); + const findChart = () => wrapper.findComponent(GlStackedColumnChart); + const findLegend = () => wrapper.findComponent(GlChartLegend); const createWrapper = (props = {}, mountingMethod = shallowMount) => mountingMethod(StackedColumnChart, { diff --git a/spec/frontend/monitoring/components/charts/time_series_spec.js b/spec/frontend/monitoring/components/charts/time_series_spec.js index feb8cacde3c..503dee7b937 100644 --- a/spec/frontend/monitoring/components/charts/time_series_spec.js +++ b/spec/frontend/monitoring/components/charts/time_series_spec.js @@ -64,7 +64,7 @@ describe('Time series component', () => { describe('With a single time series', () => { describe('general functions', () => { - const findChart = () => wrapper.find({ ref: 'chart' }); + const findChart = () => wrapper.findComponent({ ref: 'chart' }); beforeEach(async () => { createWrapper({}, mount); @@ -210,7 +210,7 @@ describe('Time series component', () => { const name = 'Metric 1'; const value = '5.556'; const dataIndex = 0; - const seriesLabel = wrapper.find(GlChartSeriesLabel); + const seriesLabel = wrapper.findComponent(GlChartSeriesLabel); expect(seriesLabel.vm.color).toBe(''); @@ -220,7 +220,11 @@ describe('Time series component', () => { ]); expect( - shallowWrapperContainsSlotText(wrapper.find(GlLineChart), 'tooltip-content', value), + shallowWrapperContainsSlotText( + wrapper.findComponent(GlLineChart), + 'tooltip-content', + value, + ), ).toBe(true); }); @@ -593,7 +597,7 @@ describe('Time series component', () => { glChartComponents.forEach((dynamicComponent) => { describe(`GitLab UI: ${dynamicComponent.chartType}`, () => { - const findChartComponent = () => wrapper.find(dynamicComponent.component); + const findChartComponent = () => wrapper.findComponent(dynamicComponent.component); beforeEach(async () => { createWrapper( @@ -651,7 +655,7 @@ describe('Time series component', () => { wrapper.vm.tooltip.commitUrl = commitUrl; await nextTick(); - const commitLink = wrapper.find(GlLink); + const commitLink = wrapper.findComponent(GlLink); expect(shallowWrapperContainsSlotText(commitLink, 'default', mockSha)).toBe(true); expect(commitLink.attributes('href')).toEqual(commitUrl); @@ -675,7 +679,9 @@ describe('Time series component', () => { let lineColors; beforeEach(() => { - lineColors = wrapper.find(GlAreaChart).vm.series.map((item) => item.lineStyle.color); + lineColors = wrapper + .findComponent(GlAreaChart) + .vm.series.map((item) => item.lineStyle.color); }); it('should contain different colors for contiguous time series', () => { @@ -685,7 +691,7 @@ describe('Time series component', () => { }); it('should match series color with tooltip label color', () => { - const labels = wrapper.findAll(GlChartSeriesLabel); + const labels = wrapper.findAllComponents(GlChartSeriesLabel); lineColors.forEach((color, index) => { const labelColor = labels.at(index).props('color'); @@ -695,7 +701,7 @@ describe('Time series component', () => { it('should match series color with legend color', () => { const legendColors = wrapper - .find(GlChartLegend) + .findComponent(GlChartLegend) .props('seriesInfo') .map((item) => item.color); @@ -708,7 +714,7 @@ describe('Time series component', () => { }); describe('legend layout', () => { - const findLegend = () => wrapper.find(GlChartLegend); + const findLegend = () => wrapper.findComponent(GlChartLegend); beforeEach(async () => { createWrapper({}, mount); diff --git a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js index 281f0f4e4fe..bb57420d406 100644 --- a/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js +++ b/spec/frontend/monitoring/components/dashboard_actions_menu_spec.js @@ -92,7 +92,7 @@ describe('Actions menu', () => { }); it('renders custom metrics form fields', () => { - expect(wrapper.find(CustomMetricsFormFields).exists()).toBe(true); + expect(wrapper.findComponent(CustomMetricsFormFields).exists()).toBe(true); }); }); diff --git a/spec/frontend/monitoring/components/dashboard_header_spec.js b/spec/frontend/monitoring/components/dashboard_header_spec.js index e28c2913949..18ccda2c41c 100644 --- a/spec/frontend/monitoring/components/dashboard_header_spec.js +++ b/spec/frontend/monitoring/components/dashboard_header_spec.js @@ -29,18 +29,19 @@ describe('Dashboard header', () => { let store; let wrapper; - const findDashboardDropdown = () => wrapper.find(DashboardsDropdown); + const findDashboardDropdown = () => wrapper.findComponent(DashboardsDropdown); - const findEnvsDropdown = () => wrapper.find({ ref: 'monitorEnvironmentsDropdown' }); - const findEnvsDropdownItems = () => findEnvsDropdown().findAll(GlDropdownItem); - const findEnvsDropdownSearch = () => findEnvsDropdown().find(GlSearchBoxByType); - const findEnvsDropdownSearchMsg = () => wrapper.find({ ref: 'monitorEnvironmentsDropdownMsg' }); - const findEnvsDropdownLoadingIcon = () => findEnvsDropdown().find(GlLoadingIcon); + const findEnvsDropdown = () => wrapper.findComponent({ ref: 'monitorEnvironmentsDropdown' }); + const findEnvsDropdownItems = () => findEnvsDropdown().findAllComponents(GlDropdownItem); + const findEnvsDropdownSearch = () => findEnvsDropdown().findComponent(GlSearchBoxByType); + const findEnvsDropdownSearchMsg = () => + wrapper.findComponent({ ref: 'monitorEnvironmentsDropdownMsg' }); + const findEnvsDropdownLoadingIcon = () => findEnvsDropdown().findComponent(GlLoadingIcon); - const findDateTimePicker = () => wrapper.find(DateTimePicker); - const findRefreshButton = () => wrapper.find(RefreshButton); + const findDateTimePicker = () => wrapper.findComponent(DateTimePicker); + const findRefreshButton = () => wrapper.findComponent(RefreshButton); - const findActionsMenu = () => wrapper.find(ActionsMenu); + const findActionsMenu = () => wrapper.findComponent(ActionsMenu); const setSearchTerm = (searchTerm) => { store.commit(`monitoringDashboard/${types.SET_ENVIRONMENTS_FILTER}`, searchTerm); diff --git a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js index f19ef6c6fb7..d71f6374967 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_builder_spec.js @@ -32,14 +32,14 @@ describe('dashboard invalid url parameters', () => { }); }; - const findForm = () => wrapper.find(GlForm); - const findTxtArea = () => findForm().find(GlFormTextarea); + const findForm = () => wrapper.findComponent(GlForm); + const findTxtArea = () => findForm().findComponent(GlFormTextarea); const findSubmitBtn = () => findForm().find('[type="submit"]'); - const findClipboardCopyBtn = () => wrapper.find({ ref: 'clipboardCopyBtn' }); - const findViewDocumentationBtn = () => wrapper.find({ ref: 'viewDocumentationBtn' }); - const findOpenRepositoryBtn = () => wrapper.find({ ref: 'openRepositoryBtn' }); - const findPanel = () => wrapper.find(DashboardPanel); - const findTimeRangePicker = () => wrapper.find(DateTimePicker); + const findClipboardCopyBtn = () => wrapper.findComponent({ ref: 'clipboardCopyBtn' }); + const findViewDocumentationBtn = () => wrapper.findComponent({ ref: 'viewDocumentationBtn' }); + const findOpenRepositoryBtn = () => wrapper.findComponent({ ref: 'openRepositoryBtn' }); + const findPanel = () => wrapper.findComponent(DashboardPanel); + const findTimeRangePicker = () => wrapper.findComponent(DateTimePicker); const findRefreshButton = () => wrapper.find('[data-testid="previewRefreshButton"]'); beforeEach(() => { @@ -192,8 +192,8 @@ describe('dashboard invalid url parameters', () => { }); it('displays an alert', () => { - expect(wrapper.find(GlAlert).exists()).toBe(true); - expect(wrapper.find(GlAlert).text()).toBe(mockError); + expect(wrapper.findComponent(GlAlert).exists()).toBe(true); + expect(wrapper.findComponent(GlAlert).text()).toBe(mockError); }); it('displays an empty dashboard panel', () => { @@ -215,11 +215,11 @@ describe('dashboard invalid url parameters', () => { }); it('displays no alert', () => { - expect(wrapper.find(GlAlert).exists()).toBe(false); + expect(wrapper.findComponent(GlAlert).exists()).toBe(false); }); it('displays panel with data', () => { - const { title, type } = wrapper.find(DashboardPanel).props('graphData'); + const { title, type } = wrapper.findComponent(DashboardPanel).props('graphData'); expect(title).toBe(mockPanel.title); expect(type).toBe(mockPanel.type); diff --git a/spec/frontend/monitoring/components/dashboard_panel_spec.js b/spec/frontend/monitoring/components/dashboard_panel_spec.js index 6f5aa8998be..d797d9e2ad0 100644 --- a/spec/frontend/monitoring/components/dashboard_panel_spec.js +++ b/spec/frontend/monitoring/components/dashboard_panel_spec.js @@ -41,11 +41,11 @@ describe('Dashboard Panel', () => { const exampleText = 'example_text'; - const findCopyLink = () => wrapper.find({ ref: 'copyChartLink' }); - const findTimeChart = () => wrapper.find({ ref: 'timeSeriesChart' }); - const findTitle = () => wrapper.find({ ref: 'graphTitle' }); - const findCtxMenu = () => wrapper.find({ ref: 'contextualMenu' }); - const findMenuItems = () => wrapper.findAll(GlDropdownItem); + const findCopyLink = () => wrapper.findComponent({ ref: 'copyChartLink' }); + const findTimeChart = () => wrapper.findComponent({ ref: 'timeSeriesChart' }); + const findTitle = () => wrapper.findComponent({ ref: 'graphTitle' }); + const findCtxMenu = () => wrapper.findComponent({ ref: 'contextualMenu' }); + const findMenuItems = () => wrapper.findAllComponents(GlDropdownItem); const findMenuItemByText = (text) => findMenuItems().filter((i) => i.text() === text); const createWrapper = (props, { mountFn = shallowMount, ...options } = {}) => { @@ -115,7 +115,7 @@ describe('Dashboard Panel', () => { }); it('renders no download csv link', () => { - expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); + expect(wrapper.findComponent({ ref: 'downloadCsvLink' }).exists()).toBe(false); }); it('does not contain graph widgets', () => { @@ -123,7 +123,7 @@ describe('Dashboard Panel', () => { }); it('The Empty Chart component is rendered and is a Vue instance', () => { - expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.findComponent(MonitorEmptyChart).exists()).toBe(true); }); }); @@ -143,7 +143,7 @@ describe('Dashboard Panel', () => { }); it('renders no download csv link', () => { - expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(false); + expect(wrapper.findComponent({ ref: 'downloadCsvLink' }).exists()).toBe(false); }); it('does not contain graph widgets', () => { @@ -151,7 +151,7 @@ describe('Dashboard Panel', () => { }); it('The Empty Chart component is rendered and is a Vue instance', () => { - expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.findComponent(MonitorEmptyChart).exists()).toBe(true); }); }); @@ -170,7 +170,7 @@ describe('Dashboard Panel', () => { it('contains graph widgets', () => { expect(findCtxMenu().exists()).toBe(true); - expect(wrapper.find({ ref: 'downloadCsvLink' }).exists()).toBe(true); + expect(wrapper.findComponent({ ref: 'downloadCsvLink' }).exists()).toBe(true); }); it('sets no clipboard copy link on dropdown by default', () => { @@ -205,12 +205,12 @@ describe('Dashboard Panel', () => { it('empty chart is rendered for empty results', () => { createWrapper({ graphData: graphDataEmpty }); - expect(wrapper.find(MonitorEmptyChart).exists()).toBe(true); + expect(wrapper.findComponent(MonitorEmptyChart).exists()).toBe(true); }); it('area chart is rendered by default', () => { createWrapper(); - expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true); + expect(wrapper.findComponent(MonitorTimeSeriesChart).exists()).toBe(true); }); describe.each` @@ -231,8 +231,8 @@ describe('Dashboard Panel', () => { }); it(`renders the chart component and binds attributes`, () => { - expect(wrapper.find(component).exists()).toBe(true); - expect(wrapper.find(component).attributes()).toMatchObject(attrs); + expect(wrapper.findComponent(component).exists()).toBe(true); + expect(wrapper.findComponent(component).attributes()).toMatchObject(attrs); }); it(`contextual menu is ${hasCtxMenu ? '' : 'not '}shown`, () => { @@ -270,7 +270,7 @@ describe('Dashboard Panel', () => { }); describe('Edit custom metric dropdown item', () => { - const findEditCustomMetricLink = () => wrapper.find({ ref: 'editMetricLink' }); + const findEditCustomMetricLink = () => wrapper.findComponent({ ref: 'editMetricLink' }); const mockEditPath = '/root/kubernetes-gke-project/prometheus/metrics/23/edit'; beforeEach(async () => { @@ -431,7 +431,7 @@ describe('Dashboard Panel', () => { }); it('it renders a time series chart with no errors', () => { - expect(wrapper.find(MonitorTimeSeriesChart).exists()).toBe(true); + expect(wrapper.findComponent(MonitorTimeSeriesChart).exists()).toBe(true); }); }); @@ -443,7 +443,7 @@ describe('Dashboard Panel', () => { it('displays a heatmap in local timezone', () => { createWrapper({ graphData: heatmapGraphData() }); - expect(wrapper.find(MonitorHeatmapChart).props('timezone')).toBe('LOCAL'); + expect(wrapper.findComponent(MonitorHeatmapChart).props('timezone')).toBe('LOCAL'); }); describe('when timezone is set to UTC', () => { @@ -458,13 +458,13 @@ describe('Dashboard Panel', () => { it('displays a heatmap with UTC', () => { createWrapper({ graphData: heatmapGraphData() }); - expect(wrapper.find(MonitorHeatmapChart).props('timezone')).toBe('UTC'); + expect(wrapper.findComponent(MonitorHeatmapChart).props('timezone')).toBe('UTC'); }); }); }); describe('Expand to full screen', () => { - const findExpandBtn = () => wrapper.find({ ref: 'expandBtn' }); + const findExpandBtn = () => wrapper.findComponent({ ref: 'expandBtn' }); describe('when there is no @expand listener', () => { it('does not show `View full screen` option', () => { @@ -492,7 +492,7 @@ describe('Dashboard Panel', () => { }); describe('When graphData contains links', () => { - const findManageLinksItem = () => wrapper.find({ ref: 'manageLinksItem' }); + const findManageLinksItem = () => wrapper.findComponent({ ref: 'manageLinksItem' }); const mockLinks = [ { url: 'https://example.com', diff --git a/spec/frontend/monitoring/components/dashboard_spec.js b/spec/frontend/monitoring/components/dashboard_spec.js index 90171cfc65e..608404e5c5b 100644 --- a/spec/frontend/monitoring/components/dashboard_spec.js +++ b/spec/frontend/monitoring/components/dashboard_spec.js @@ -97,8 +97,10 @@ describe('Dashboard', () => { createShallowWrapper({ hasMetrics: true }); await nextTick(); - expect(wrapper.find(EmptyState).exists()).toBe(true); - expect(wrapper.find(EmptyState).props('selectedState')).toBe(dashboardEmptyStates.LOADING); + expect(wrapper.findComponent(EmptyState).exists()).toBe(true); + expect(wrapper.findComponent(EmptyState).props('selectedState')).toBe( + dashboardEmptyStates.LOADING, + ); }); it('hides the group panels when showPanels is false', async () => { @@ -126,7 +128,7 @@ describe('Dashboard', () => { describe('panel containers layout', () => { const findPanelLayoutWrapperAt = (index) => { return wrapper - .find(GraphGroup) + .findComponent(GraphGroup) .findAll('[data-testid="dashboard-panel-layout-wrapper"]') .at(index); }; @@ -366,7 +368,7 @@ describe('Dashboard', () => { }); describe('when all panels in the first group are loading', () => { - const findGroupAt = (i) => wrapper.findAll(GraphGroup).at(i); + const findGroupAt = (i) => wrapper.findAllComponents(GraphGroup).at(i); beforeEach(async () => { setupStoreWithDashboard(store); @@ -409,7 +411,7 @@ describe('Dashboard', () => { setupStoreWithData(store); await nextTick(); - wrapper.findAll(GraphGroup).wrappers.forEach((groupWrapper) => { + wrapper.findAllComponents(GraphGroup).wrappers.forEach((groupWrapper) => { expect(groupWrapper.props('isLoading')).toBe(false); }); }); @@ -443,7 +445,7 @@ describe('Dashboard', () => { }); describe('single panel expands to "full screen" mode', () => { - const findExpandedPanel = () => wrapper.find({ ref: 'expandedPanel' }); + const findExpandedPanel = () => wrapper.findComponent({ ref: 'expandedPanel' }); describe('when the panel is not expanded', () => { beforeEach(async () => { @@ -457,7 +459,7 @@ describe('Dashboard', () => { }); it('can set a panel as expanded', () => { - const panel = wrapper.findAll(DashboardPanel).at(1); + const panel = wrapper.findAllComponents(DashboardPanel).at(1); jest.spyOn(store, 'dispatch'); @@ -503,7 +505,7 @@ describe('Dashboard', () => { }); it('displays a single panel and others are hidden', () => { - const panels = wrapper.findAll(MockPanel); + const panels = wrapper.findAllComponents(MockPanel); const visiblePanels = panels.filter((w) => w.isVisible()); expect(findExpandedPanel().isVisible()).toBe(true); @@ -523,7 +525,7 @@ describe('Dashboard', () => { }); it('restores full dashboard by clicking `back`', () => { - wrapper.find({ ref: 'goBackBtn' }).vm.$emit('click'); + wrapper.findComponent({ ref: 'goBackBtn' }).vm.$emit('click'); expect(store.dispatch).toHaveBeenCalledWith( 'monitoringDashboard/clearExpandedPanel', @@ -551,21 +553,21 @@ describe('Dashboard', () => { }); it('shows a group empty area', () => { - const emptyGroup = wrapper.findAll({ ref: 'empty-group' }); + const emptyGroup = wrapper.findAllComponents({ ref: 'empty-group' }); expect(emptyGroup).toHaveLength(1); expect(emptyGroup.is(GroupEmptyState)).toBe(true); }); it('group empty area displays a NO_DATA state', () => { - expect(wrapper.findAll({ ref: 'empty-group' }).at(0).props('selectedState')).toEqual( - metricStates.NO_DATA, - ); + expect( + wrapper.findAllComponents({ ref: 'empty-group' }).at(0).props('selectedState'), + ).toEqual(metricStates.NO_DATA); }); }); describe('drag and drop function', () => { - const findDraggables = () => wrapper.findAll(VueDraggable); + const findDraggables = () => wrapper.findAllComponents(VueDraggable); const findEnabledDraggables = () => findDraggables().filter((f) => !f.attributes('disabled')); const findDraggablePanels = () => wrapper.findAll('.js-draggable-panel'); const findRearrangeButton = () => wrapper.find('.js-rearrange-button'); @@ -677,7 +679,7 @@ describe('Dashboard', () => { }); it('hides dashboard header by default', () => { - expect(wrapper.find({ ref: 'prometheusGraphsHeader' }).exists()).toEqual(false); + expect(wrapper.findComponent({ ref: 'prometheusGraphsHeader' }).exists()).toEqual(false); }); it('renders correctly', () => { @@ -742,7 +744,7 @@ describe('Dashboard', () => { const panelIndex = 1; // skip expanded panel const getClipboardTextFirstPanel = () => - wrapper.findAll(DashboardPanel).at(panelIndex).props('clipboardText'); + wrapper.findAllComponents(DashboardPanel).at(panelIndex).props('clipboardText'); beforeEach(async () => { setupStoreWithData(store); @@ -770,7 +772,7 @@ describe('Dashboard', () => { // While the recommendation in the documentation is to test // with a data-testid attribute, I want to make sure that // the dashboard panels have a ref attribute set. - const getDashboardPanel = () => wrapper.find({ ref: panelRef }); + const getDashboardPanel = () => wrapper.findComponent({ ref: panelRef }); beforeEach(async () => { setupStoreWithData(store); diff --git a/spec/frontend/monitoring/components/dashboard_url_time_spec.js b/spec/frontend/monitoring/components/dashboard_url_time_spec.js index 64c48100b31..a327e234581 100644 --- a/spec/frontend/monitoring/components/dashboard_url_time_spec.js +++ b/spec/frontend/monitoring/components/dashboard_url_time_spec.js @@ -35,7 +35,8 @@ describe('dashboard invalid url parameters', () => { }); }; - const findDateTimePicker = () => wrapper.find(DashboardHeader).find({ ref: 'dateTimePicker' }); + const findDateTimePicker = () => + wrapper.findComponent(DashboardHeader).findComponent({ ref: 'dateTimePicker' }); beforeEach(() => { store = createStore(); diff --git a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js index f6d30384847..721992e710a 100644 --- a/spec/frontend/monitoring/components/dashboards_dropdown_spec.js +++ b/spec/frontend/monitoring/components/dashboards_dropdown_spec.js @@ -33,11 +33,11 @@ describe('DashboardsDropdown', () => { }); } - const findItems = () => wrapper.findAll(GlDropdownItem); - const findItemAt = (i) => wrapper.findAll(GlDropdownItem).at(i); - const findSearchInput = () => wrapper.find({ ref: 'monitorDashboardsDropdownSearch' }); - const findNoItemsMsg = () => wrapper.find({ ref: 'monitorDashboardsDropdownMsg' }); - const findStarredListDivider = () => wrapper.find({ ref: 'starredListDivider' }); + const findItems = () => wrapper.findAllComponents(GlDropdownItem); + const findItemAt = (i) => wrapper.findAllComponents(GlDropdownItem).at(i); + const findSearchInput = () => wrapper.findComponent({ ref: 'monitorDashboardsDropdownSearch' }); + const findNoItemsMsg = () => wrapper.findComponent({ ref: 'monitorDashboardsDropdownMsg' }); + const findStarredListDivider = () => wrapper.findComponent({ ref: 'starredListDivider' }); // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details // eslint-disable-next-line no-restricted-syntax const setSearchTerm = (searchTerm) => wrapper.setData({ searchTerm }); @@ -127,7 +127,7 @@ describe('DashboardsDropdown', () => { }); it('displays a star icon', () => { - const star = findItemAt(0).find(GlIcon); + const star = findItemAt(0).findComponent(GlIcon); expect(star.exists()).toBe(true); expect(star.attributes('name')).toBe('star'); }); @@ -148,7 +148,7 @@ describe('DashboardsDropdown', () => { }); it('displays no star icon', () => { - const star = findItemAt(0).find(GlIcon); + const star = findItemAt(0).findComponent(GlIcon); expect(star.exists()).toBe(false); }); diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js index 0dd3afd7c83..755204dc721 100644 --- a/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js +++ b/spec/frontend/monitoring/components/duplicate_dashboard_form_spec.js @@ -18,7 +18,7 @@ const createMountedWrapper = (props = {}) => { describe('DuplicateDashboardForm', () => { const defaultBranch = 'main'; - const findByRef = (ref) => wrapper.find({ ref }); + const findByRef = (ref) => wrapper.findComponent({ ref }); const setValue = (ref, val) => { findByRef(ref).setValue(val); }; diff --git a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js index 7e7a7a66d77..3032c236741 100644 --- a/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js +++ b/spec/frontend/monitoring/components/duplicate_dashboard_modal_spec.js @@ -44,9 +44,9 @@ describe('duplicate dashboard modal', () => { }); } - const findAlert = () => wrapper.find(GlAlert); - const findModal = () => wrapper.find(GlModal); - const findDuplicateDashboardForm = () => wrapper.find(DuplicateDashboardForm); + const findAlert = () => wrapper.findComponent(GlAlert); + const findModal = () => wrapper.findComponent(GlModal); + const findDuplicateDashboardForm = () => wrapper.findComponent(DuplicateDashboardForm); beforeEach(() => { mockDashboards = dashboardGitResponse; @@ -74,7 +74,7 @@ describe('duplicate dashboard modal', () => { expect(okEvent.preventDefault).toHaveBeenCalled(); expect(wrapper.emitted().dashboardDuplicated).toBeTruthy(); expect(wrapper.emitted().dashboardDuplicated[0]).toEqual([dashboardGitResponse[0]]); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); expect(wrapper.vm.$refs.duplicateDashboardModal.hide).toHaveBeenCalled(); expect(findAlert().exists()).toBe(false); }); @@ -92,7 +92,7 @@ describe('duplicate dashboard modal', () => { expect(findAlert().exists()).toBe(true); expect(findAlert().text()).toBe(errMsg); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); expect(wrapper.vm.$refs.duplicateDashboardModal.hide).not.toHaveBeenCalled(); }); @@ -102,7 +102,7 @@ describe('duplicate dashboard modal', () => { commitMessage: 'A commit message', }; - findModal().find(DuplicateDashboardForm).vm.$emit('change', formVals); + findModal().findComponent(DuplicateDashboardForm).vm.$emit('change', formVals); // Binding's second argument contains the modal id expect(wrapper.vm.form).toEqual(formVals); diff --git a/spec/frontend/monitoring/components/embeds/embed_group_spec.js b/spec/frontend/monitoring/components/embeds/embed_group_spec.js index 47366b345a8..6695353bdb5 100644 --- a/spec/frontend/monitoring/components/embeds/embed_group_spec.js +++ b/spec/frontend/monitoring/components/embeds/embed_group_spec.js @@ -58,14 +58,14 @@ describe('Embed Group', () => { metricsWithDataGetter.mockReturnValue([]); mountComponent(); - expect(wrapper.find(GlCard).isVisible()).toBe(false); + expect(wrapper.findComponent(GlCard).isVisible()).toBe(false); }); it('shows the component when chart data is loaded', () => { metricsWithDataGetter.mockReturnValue([1]); mountComponent(); - expect(wrapper.find(GlCard).isVisible()).toBe(true); + expect(wrapper.findComponent(GlCard).isVisible()).toBe(true); }); it('is expanded by default', () => { @@ -79,7 +79,7 @@ describe('Embed Group', () => { metricsWithDataGetter.mockReturnValue([1]); mountComponent({ shallow: false, stubs: { MetricEmbed: true } }); - wrapper.find(GlButton).trigger('click'); + wrapper.findComponent(GlButton).trigger('click'); await nextTick(); expect(wrapper.find('.gl-card-body').classes()).toContain('d-none'); @@ -93,11 +93,11 @@ describe('Embed Group', () => { }); it('renders an Embed component', () => { - expect(wrapper.find(MetricEmbed).exists()).toBe(true); + expect(wrapper.findComponent(MetricEmbed).exists()).toBe(true); }); it('passes the correct props to the Embed component', () => { - expect(wrapper.find(MetricEmbed).props()).toEqual(singleEmbedProps()); + expect(wrapper.findComponent(MetricEmbed).props()).toEqual(singleEmbedProps()); }); it('adds the monitoring dashboard module', () => { @@ -112,7 +112,7 @@ describe('Embed Group', () => { }); it('passes the correct props to the dashboard Embed component', () => { - expect(wrapper.find(MetricEmbed).props()).toEqual(dashboardEmbedProps()); + expect(wrapper.findComponent(MetricEmbed).props()).toEqual(dashboardEmbedProps()); }); it('adds the monitoring dashboard module', () => { @@ -127,11 +127,11 @@ describe('Embed Group', () => { }); it('creates Embed components', () => { - expect(wrapper.findAll(MetricEmbed)).toHaveLength(2); + expect(wrapper.findAllComponents(MetricEmbed)).toHaveLength(2); }); it('passes the correct props to the Embed components', () => { - expect(wrapper.findAll(MetricEmbed).wrappers.map((item) => item.props())).toEqual( + expect(wrapper.findAllComponents(MetricEmbed).wrappers.map((item) => item.props())).toEqual( multipleEmbedProps(), ); }); @@ -147,14 +147,14 @@ describe('Embed Group', () => { metricsWithDataGetter.mockReturnValue([1]); mountComponent({ shallow: false, stubs: { MetricEmbed: true } }); - expect(wrapper.find(GlButton).text()).toBe('Hide chart'); + expect(wrapper.findComponent(GlButton).text()).toBe('Hide chart'); }); it('has a plural label when there are multiple embeds', () => { metricsWithDataGetter.mockReturnValue([2]); mountComponent({ shallow: false, stubs: { MetricEmbed: true } }); - expect(wrapper.find(GlButton).text()).toBe('Hide charts'); + expect(wrapper.findComponent(GlButton).text()).toBe('Hide charts'); }); }); }); diff --git a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js index f9f1be4f277..beff3da2baf 100644 --- a/spec/frontend/monitoring/components/embeds/metric_embed_spec.js +++ b/spec/frontend/monitoring/components/embeds/metric_embed_spec.js @@ -64,7 +64,7 @@ describe('MetricEmbed', () => { it('shows an empty state when no metrics are present', () => { expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(DashboardPanel).exists()).toBe(false); + expect(wrapper.findComponent(DashboardPanel).exists()).toBe(false); }); }); @@ -92,12 +92,12 @@ describe('MetricEmbed', () => { it('shows a chart when metrics are present', () => { expect(wrapper.find('.metrics-embed').exists()).toBe(true); - expect(wrapper.find(DashboardPanel).exists()).toBe(true); - expect(wrapper.findAll(DashboardPanel).length).toBe(2); + expect(wrapper.findComponent(DashboardPanel).exists()).toBe(true); + expect(wrapper.findAllComponents(DashboardPanel).length).toBe(2); }); it('includes groupId with dashboardUrl', () => { - expect(wrapper.find(DashboardPanel).props('groupId')).toBe(TEST_HOST); + expect(wrapper.findComponent(DashboardPanel).props('groupId')).toBe(TEST_HOST); }); }); }); diff --git a/spec/frontend/monitoring/components/empty_state_spec.js b/spec/frontend/monitoring/components/empty_state_spec.js index 1ecb101574b..ddefa8c5cd0 100644 --- a/spec/frontend/monitoring/components/empty_state_spec.js +++ b/spec/frontend/monitoring/components/empty_state_spec.js @@ -25,8 +25,8 @@ describe('EmptyState', () => { selectedState: dashboardEmptyStates.LOADING, }); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - expect(wrapper.find(GlEmptyState).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlEmptyState).exists()).toBe(false); }); it('shows gettingStarted state', () => { diff --git a/spec/frontend/monitoring/components/graph_group_spec.js b/spec/frontend/monitoring/components/graph_group_spec.js index 31f52f6627b..104263e73e0 100644 --- a/spec/frontend/monitoring/components/graph_group_spec.js +++ b/spec/frontend/monitoring/components/graph_group_spec.js @@ -6,10 +6,10 @@ import GraphGroup from '~/monitoring/components/graph_group.vue'; describe('Graph group component', () => { let wrapper; - const findGroup = () => wrapper.find({ ref: 'graph-group' }); - const findContent = () => wrapper.find({ ref: 'graph-group-content' }); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); - const findCaretIcon = () => wrapper.find(GlIcon); + const findGroup = () => wrapper.findComponent({ ref: 'graph-group' }); + const findContent = () => wrapper.findComponent({ ref: 'graph-group-content' }); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findCaretIcon = () => wrapper.findComponent(GlIcon); const findToggleButton = () => wrapper.find('[data-testid="group-toggle-button"]'); const createComponent = (propsData) => { diff --git a/spec/frontend/monitoring/components/group_empty_state_spec.js b/spec/frontend/monitoring/components/group_empty_state_spec.js index 1dd2ed4e141..e3cd26b0e48 100644 --- a/spec/frontend/monitoring/components/group_empty_state_spec.js +++ b/spec/frontend/monitoring/components/group_empty_state_spec.js @@ -45,7 +45,7 @@ describe('GroupEmptyState', () => { }); it('passes the expected props to GlEmptyState', () => { - expect(wrapper.find(GlEmptyState).props()).toMatchSnapshot(); + expect(wrapper.findComponent(GlEmptyState).props()).toMatchSnapshot(); }); }); }); diff --git a/spec/frontend/monitoring/components/links_section_spec.js b/spec/frontend/monitoring/components/links_section_spec.js index c9b5aeeecb8..94938e7f459 100644 --- a/spec/frontend/monitoring/components/links_section_spec.js +++ b/spec/frontend/monitoring/components/links_section_spec.js @@ -21,7 +21,7 @@ describe('Links Section component', () => { links, }; }; - const findLinks = () => wrapper.findAll(GlLink); + const findLinks = () => wrapper.findAllComponents(GlLink); beforeEach(() => { store = createStore(); diff --git a/spec/frontend/monitoring/components/refresh_button_spec.js b/spec/frontend/monitoring/components/refresh_button_spec.js index 0e45cc021c5..e00736954a9 100644 --- a/spec/frontend/monitoring/components/refresh_button_spec.js +++ b/spec/frontend/monitoring/components/refresh_button_spec.js @@ -15,9 +15,9 @@ describe('RefreshButton', () => { wrapper = shallowMount(RefreshButton, { store, ...options }); }; - const findRefreshBtn = () => wrapper.find(GlButton); - const findDropdown = () => wrapper.find(GlDropdown); - const findOptions = () => findDropdown().findAll(GlDropdownItem); + const findRefreshBtn = () => wrapper.findComponent(GlButton); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findOptions = () => findDropdown().findAllComponents(GlDropdownItem); const findOptionAt = (index) => findOptions().at(index); const expectFetchDataToHaveBeenCalledTimes = (times) => { diff --git a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js index 643bbb39f04..012e2e9c3e2 100644 --- a/spec/frontend/monitoring/components/variables/dropdown_field_spec.js +++ b/spec/frontend/monitoring/components/variables/dropdown_field_spec.js @@ -27,8 +27,8 @@ describe('Custom variable component', () => { }); }; - const findDropdown = () => wrapper.find(GlDropdown); - const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); it('renders dropdown element when all necessary props are passed', () => { createShallowWrapper(); diff --git a/spec/frontend/monitoring/components/variables_section_spec.js b/spec/frontend/monitoring/components/variables_section_spec.js index 64b93bd3027..d6f8aac99aa 100644 --- a/spec/frontend/monitoring/components/variables_section_spec.js +++ b/spec/frontend/monitoring/components/variables_section_spec.js @@ -24,8 +24,8 @@ describe('Metrics dashboard/variables section component', () => { }); }; - const findTextInputs = () => wrapper.findAll(TextField); - const findCustomInputs = () => wrapper.findAll(DropdownField); + const findTextInputs = () => wrapper.findAllComponents(TextField); + const findCustomInputs = () => wrapper.findAllComponents(DropdownField); beforeEach(() => { store = createStore(); diff --git a/spec/frontend/monitoring/pages/panel_new_page_spec.js b/spec/frontend/monitoring/pages/panel_new_page_spec.js index c89cbc52bcb..fa112fca2db 100644 --- a/spec/frontend/monitoring/pages/panel_new_page_spec.js +++ b/spec/frontend/monitoring/pages/panel_new_page_spec.js @@ -41,8 +41,8 @@ describe('monitoring/pages/panel_new_page', () => { }); }; - const findBackButton = () => wrapper.find(GlButtonStub); - const findPanelBuilder = () => wrapper.find(DashboardPanelBuilder); + const findBackButton = () => wrapper.findComponent(GlButtonStub); + const findPanelBuilder = () => wrapper.findComponent(DashboardPanelBuilder); beforeEach(() => { store = createStore(); diff --git a/spec/frontend/monitoring/router_spec.js b/spec/frontend/monitoring/router_spec.js index 7758dd351b7..368bd955fb3 100644 --- a/spec/frontend/monitoring/router_spec.js +++ b/spec/frontend/monitoring/router_spec.js @@ -61,8 +61,8 @@ describe('Monitoring router', () => { currentDashboard, }); - expect(wrapper.find(DashboardPage).exists()).toBe(true); - expect(wrapper.find(DashboardPage).find(Dashboard).exists()).toBe(true); + expect(wrapper.findComponent(DashboardPage).exists()).toBe(true); + expect(wrapper.findComponent(DashboardPage).findComponent(Dashboard).exists()).toBe(true); }); }); @@ -84,8 +84,8 @@ describe('Monitoring router', () => { currentDashboard, }); - expect(wrapper.find(DashboardPage).exists()).toBe(true); - expect(wrapper.find(DashboardPage).find(Dashboard).exists()).toBe(true); + expect(wrapper.findComponent(DashboardPage).exists()).toBe(true); + expect(wrapper.findComponent(DashboardPage).findComponent(Dashboard).exists()).toBe(true); }); }); @@ -100,7 +100,7 @@ describe('Monitoring router', () => { const wrapper = createWrapper(BASE_PATH, path); expect(wrapper.vm.$route.params.dashboard).toBe(currentDashboard); - expect(wrapper.find(PanelNewPage).exists()).toBe(true); + expect(wrapper.findComponent(PanelNewPage).exists()).toBe(true); }); }); }); diff --git a/spec/frontend/notes/components/note_body_spec.js b/spec/frontend/notes/components/note_body_spec.js index 0f765a8da87..f96394d9e86 100644 --- a/spec/frontend/notes/components/note_body_spec.js +++ b/spec/frontend/notes/components/note_body_spec.js @@ -7,7 +7,6 @@ import NoteAwardsList from '~/notes/components/note_awards_list.vue'; import NoteForm from '~/notes/components/note_form.vue'; import createStore from '~/notes/stores'; import notes from '~/notes/stores/modules/index'; -import { INTERNAL_NOTE_CLASSES } from '~/notes/constants'; import Suggestions from '~/vue_shared/components/markdown/suggestions.vue'; @@ -59,22 +58,10 @@ describe('issue_note_body component', () => { expect(wrapper.findComponent(NoteAwardsList).exists()).toBe(true); }); - it('should not have internal note classes', () => { - expect(wrapper.findByTestId('note-internal-container').classes()).not.toEqual( - INTERNAL_NOTE_CLASSES, - ); - }); - describe('isInternalNote', () => { beforeEach(() => { wrapper = createComponent({ props: { isInternalNote: true } }); }); - - it('should have internal note classes', () => { - expect(wrapper.findByTestId('note-internal-container').classes()).toEqual( - INTERNAL_NOTE_CLASSES, - ); - }); }); describe('isEditing', () => { @@ -110,12 +97,6 @@ describe('issue_note_body component', () => { beforeEach(() => { wrapper.setProps({ isInternalNote: true }); }); - - it('should not have internal note classes', () => { - expect(wrapper.findByTestId('note-internal-container').classes()).not.toEqual( - INTERNAL_NOTE_CLASSES, - ); - }); }); }); diff --git a/spec/frontend/notes/components/noteable_note_spec.js b/spec/frontend/notes/components/noteable_note_spec.js index c2c3643abb0..e049c5bc0c8 100644 --- a/spec/frontend/notes/components/noteable_note_spec.js +++ b/spec/frontend/notes/components/noteable_note_spec.js @@ -290,6 +290,20 @@ describe('issue_note', () => { }); }); + describe('internal note', () => { + it('has internal note class for internal notes', () => { + createWrapper({ note: { ...note, confidential: true } }); + + expect(wrapper.classes()).toContain('internal-note'); + }); + + it('does not have internal note class for external notes', () => { + createWrapper({ note }); + + expect(wrapper.classes()).not.toContain('internal-note'); + }); + }); + describe('cancel edit', () => { beforeEach(() => { createWrapper(); diff --git a/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb b/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb new file mode 100644 index 00000000000..ef1b18f0a11 --- /dev/null +++ b/spec/graphql/resolvers/projects/fork_targets_resolver_spec.rb @@ -0,0 +1,49 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Resolvers::Projects::ForkTargetsResolver do + include GraphqlHelpers + + let_it_be(:group) { create(:group, path: 'namespace-group') } + let_it_be(:another_group) { create(:group, path: 'namespace-another-group') } + let_it_be(:project) { create(:project, :private, group: group) } + let_it_be(:user) { create(:user, username: 'namespace-user', maintainer_projects: [project]) } + + let(:args) { { search: 'namespace' } } + + describe '#resolve' do + before_all do + group.add_owner(user) + another_group.add_owner(user) + end + + it 'returns forkable namespaces' do + expect_next_instance_of(ForkTargetsFinder) do |finder| + expect(finder).to receive(:execute).with(args).and_call_original + end + + expect(resolve_targets(args).items).to match_array([user.namespace, project.namespace, another_group]) + end + end + + context 'when a user cannot fork the project' do + let(:user) { create(:user) } + + it 'does not return results' do + project.add_guest(user) + + expect(resolve_targets(args)).to be_nil + end + end + + def resolve_targets(args, opts = {}) + field_options = described_class.field_options.merge( + owner: resolver_parent, + name: 'field_value' + ).merge(opts) + + field = ::Types::BaseField.new(**field_options) + resolve_field(field, project, args: args, ctx: { current_user: user }, object_type: resolver_parent) + end +end diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 94e6a90e8db..5ff7653ce39 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -36,7 +36,8 @@ RSpec.describe GitlabSchema.types['Project'] do pipeline_analytics squash_read_only sast_ci_configuration cluster_agent cluster_agents agent_configurations ci_template timelogs merge_commit_template squash_commit_template work_item_types - recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables timelog_categories + recent_issue_boards ci_config_path_or_default packages_cleanup_policy ci_variables + timelog_categories fork_targets ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/models/customer_relations/contact_spec.rb b/spec/models/customer_relations/contact_spec.rb index 27f3c4d6e62..487af404a7c 100644 --- a/spec/models/customer_relations/contact_spec.rb +++ b/spec/models/customer_relations/contact_spec.rb @@ -232,7 +232,7 @@ RSpec.describe CustomerRelations::Contact, type: :model do create_list(:contact, 2, group: group, state: 'inactive') end - it 'returns only active contacts' do + it 'returns correct contact counts' do counts = group.contacts.counts_by_state expect(counts['active']).to be(3) diff --git a/spec/requests/api/graphql/project/fork_targets_spec.rb b/spec/requests/api/graphql/project/fork_targets_spec.rb new file mode 100644 index 00000000000..b21a11ff4dc --- /dev/null +++ b/spec/requests/api/graphql/project/fork_targets_spec.rb @@ -0,0 +1,69 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting a list of fork targets for a project' do + include GraphqlHelpers + + let_it_be(:group) { create(:group) } + let_it_be(:another_group) { create(:group) } + let_it_be(:project) { create(:project, :private, group: group) } + let_it_be(:user) { create(:user, developer_projects: [project]) } + + let(:current_user) { user } + let(:fields) do + <<~GRAPHQL + forkTargets{ + nodes { id name fullPath visibility } + } + GRAPHQL + end + + let(:query) do + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + fields + ) + end + + before_all do + group.add_owner(user) + another_group.add_owner(user) + end + + context 'when user has access to the project' do + before do + post_graphql(query, current_user: current_user) + end + + it_behaves_like 'a working graphql query' + + it 'returns fork targets for the project' do + expect(graphql_data.dig('project', 'forkTargets', 'nodes')).to match_array( + [user.namespace, project.namespace, another_group].map do |target| + hash_including( + { + 'id' => target.to_global_id.to_s, + 'name' => target.name, + 'fullPath' => target.full_path, + 'visibility' => target.visibility + } + ) + end + ) + end + end + + context "when user doesn't have access to the project" do + let(:current_user) { create(:user) } + + before do + post_graphql(query, current_user: current_user) + end + + it 'does not return the project' do + expect(graphql_data).to eq('project' => nil) + end + end +end |