diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-28 12:09:30 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-28 12:09:30 +0000 |
commit | 73a14e23da782bbafad03a0cfe9fcab8f44f13f2 (patch) | |
tree | fa678e812e8ffe93be01c41130fc9fd6ccc4ee26 | |
parent | 801820054081d4b795e6037a1c3e3d340dd831df (diff) | |
download | gitlab-ce-73a14e23da782bbafad03a0cfe9fcab8f44f13f2.tar.gz |
Add latest changes from gitlab-org/gitlab@master
127 files changed, 1152 insertions, 1095 deletions
@@ -365,7 +365,7 @@ group :development, :test do gem 'bullet', '~> 6.1.3' gem 'pry-byebug' gem 'pry-rails', '~> 0.3.9' - gem 'pry-shell', '~> 0.5.0' + gem 'pry-shell', '~> 0.5.1' gem 'awesome_print', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 723b786a02e..41843a1daf9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1000,7 +1000,7 @@ GEM pry (~> 0.13.0) pry-rails (0.3.9) pry (>= 0.10.4) - pry-shell (0.5.0) + pry-shell (0.5.1) pry (~> 0.13.0) tty-markdown tty-prompt @@ -1669,7 +1669,7 @@ DEPENDENCIES prometheus-client-mmap (~> 0.16) pry-byebug pry-rails (~> 0.3.9) - pry-shell (~> 0.5.0) + pry-shell (~> 0.5.1) puma (~> 5.6.4) puma_worker_killer (~> 0.3.1) rack (~> 2.2.4) diff --git a/app/assets/javascripts/lib/utils/yaml.js b/app/assets/javascripts/lib/utils/yaml.js index 9270d388342..48f34624140 100644 --- a/app/assets/javascripts/lib/utils/yaml.js +++ b/app/assets/javascripts/lib/utils/yaml.js @@ -16,18 +16,17 @@ function getPath(ancestry) { function getFirstChildNode(collection) { let firstChildKey; - let type; - switch (collection.constructor.name) { - case 'YAMLSeq': // eslint-disable-line @gitlab/require-i18n-strings - return collection.items.find((i) => isNode(i)); - case 'YAMLMap': // eslint-disable-line @gitlab/require-i18n-strings - firstChildKey = collection.items[0]?.key; - if (!firstChildKey) return undefined; - return isScalar(firstChildKey) ? firstChildKey : new Scalar(firstChildKey); - default: - type = collection.constructor?.name || typeof collection; - throw Error(`Cannot identify a child Node for type ${type}`); + if (isSeq(collection)) { + return collection.items.find((i) => isNode(i)); } + if (isMap(collection)) { + firstChildKey = collection.items[0]?.key; + if (!firstChildKey) return undefined; + return isScalar(firstChildKey) ? firstChildKey : new Scalar(firstChildKey); + } + throw Error( + `Cannot identify a child Node for Collection. Expecting a YAMLMap or a YAMLSeq. Got: ${collection}`, + ); } function moveMetaPropsToFirstChildNode(collection) { diff --git a/app/assets/javascripts/runner/components/stat/runner_count.vue b/app/assets/javascripts/runner/components/stat/runner_count.vue index af18b203f90..37c6f922f9a 100644 --- a/app/assets/javascripts/runner/components/stat/runner_count.vue +++ b/app/assets/javascripts/runner/components/stat/runner_count.vue @@ -1,8 +1,9 @@ <script> import { fetchPolicies } from '~/lib/graphql'; +import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql'; +import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql'; + import { captureException } from '../../sentry_utils'; -import allRunnersCountQuery from '../../graphql/list/all_runners_count.query.graphql'; -import groupRunnersCountQuery from '../../graphql/list/group_runners_count.query.graphql'; import { INSTANCE_TYPE, GROUP_TYPE } from '../../constants'; /** @@ -38,7 +39,7 @@ export default { variables: { type: Object, required: false, - default: () => {}, + default: () => ({}), }, skip: { type: Boolean, diff --git a/app/assets/javascripts/runner/components/stat/runner_single_stat.vue b/app/assets/javascripts/runner/components/stat/runner_single_stat.vue new file mode 100644 index 00000000000..ae732b052ac --- /dev/null +++ b/app/assets/javascripts/runner/components/stat/runner_single_stat.vue @@ -0,0 +1,41 @@ +<script> +import { GlSingleStat } from '@gitlab/ui/dist/charts'; +import { formatNumber } from '~/locale'; +import RunnerCount from './runner_count.vue'; + +export default { + components: { + GlSingleStat, + RunnerCount, + }, + props: { + scope: { + type: String, + required: true, + }, + variables: { + type: Object, + required: false, + default: () => ({}), + }, + skip: { + type: Boolean, + required: false, + default: false, + }, + }, + methods: { + formattedValue(value) { + if (typeof value === 'number') { + return formatNumber(value); + } + return '-'; + }, + }, +}; +</script> +<template> + <runner-count #default="{ count }" :scope="scope" :variables="variables" :skip="skip"> + <gl-single-stat v-bind="$attrs" :value="formattedValue(count)" /> + </runner-count> +</template> diff --git a/app/assets/javascripts/runner/components/stat/runner_stats.vue b/app/assets/javascripts/runner/components/stat/runner_stats.vue index 9e1ca9ba4ee..2ee7737513b 100644 --- a/app/assets/javascripts/runner/components/stat/runner_stats.vue +++ b/app/assets/javascripts/runner/components/stat/runner_stats.vue @@ -1,12 +1,12 @@ <script> import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants'; -import RunnerCount from './runner_count.vue'; import RunnerStatusStat from './runner_status_stat.vue'; export default { components: { - RunnerCount, RunnerStatusStat, + RunnerUpgradeStatusStats: () => + import('ee_component/runner/components/stat/runner_upgrade_status_stats.vue'), }, props: { scope: { @@ -16,13 +16,10 @@ export default { variables: { type: Object, required: false, - default: () => {}, + default: () => ({}), }, }, methods: { - countVariables(vars) { - return { ...this.variables, ...vars }; - }, statusCountSkip(status) { // Show an empty result when we already filter by another status return this.variables.status && this.variables.status !== status; @@ -32,16 +29,20 @@ export default { }; </script> <template> - <div class="gl-display-flex gl-py-6"> - <runner-count + <div class="gl-display-flex gl-flex-wrap gl-py-6"> + <runner-status-stat v-for="status in $options.STATUS_LIST" - #default="{ count }" :key="status" + class="gl-px-5" + :variables="variables" + :scope="scope" + :status="status" + /> + + <runner-upgrade-status-stats + class="gl-display-contents" :scope="scope" - :variables="countVariables({ status })" - :skip="statusCountSkip(status)" - > - <runner-status-stat class="gl-px-5" :status="status" :value="count" /> - </runner-count> + :variables="variables" + /> </div> </template> diff --git a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue index b77bbe15541..47979d76770 100644 --- a/app/assets/javascripts/runner/components/stat/runner_status_stat.vue +++ b/app/assets/javascripts/runner/components/stat/runner_status_stat.vue @@ -1,17 +1,21 @@ <script> -import { GlSingleStat } from '@gitlab/ui/dist/charts'; -import { s__, formatNumber } from '~/locale'; +import { s__ } from '~/locale'; import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '../../constants'; +import RunnerSingleStat from './runner_single_stat.vue'; export default { components: { - GlSingleStat, + RunnerSingleStat, }, props: { - value: { - type: Number, + scope: { + type: String, + required: true, + }, + variables: { + type: Object, required: false, - default: null, + default: () => ({}), }, status: { type: String, @@ -19,13 +23,16 @@ export default { }, }, computed: { - formattedValue() { - if (typeof this.value === 'number') { - return formatNumber(this.value); - } - return '-'; + countVariables() { + return { ...this.variables, status: this.status }; + }, + skip() { + // Status are mutually exclusive, skip displaying this total + // when filtering by an status different to this one + const { status } = this.variables; + return status && status !== this.status; }, - stat() { + statProps() { switch (this.status) { case STATUS_ONLINE: return { @@ -55,11 +62,11 @@ export default { }; </script> <template> - <gl-single-stat - v-if="stat" - :value="formattedValue" - :variant="stat.variant" - :title="stat.title" - :meta-text="stat.metaText" + <runner-single-stat + v-if="statProps" + v-bind="statProps" + :scope="scope" + :variables="countVariables" + :skip="skip" /> </template> diff --git a/app/views/groups/settings/_advanced.html.haml b/app/views/groups/settings/_advanced.html.haml index 3624ff2bcb3..8fa8eeea3cd 100644 --- a/app/views/groups/settings/_advanced.html.haml +++ b/app/views/groups/settings/_advanced.html.haml @@ -8,7 +8,7 @@ .form-group %p = s_("GroupSettings|Changing a group's URL can have unintended side effects.") - = link_to _('Learn more.'), help_page_path('user/group/index', anchor: 'change-a-groups-path'), target: '_blank', rel: 'noopener noreferrer' + = link_to _('Learn more.'), help_page_path('user/group/manage', anchor: 'change-a-groups-path'), target: '_blank', rel: 'noopener noreferrer' .input-group.gl-field-error-anchor .group-root-path.input-group-prepend.has-tooltip{ title: group_path(@group), :'data-placement' => 'bottom' } diff --git a/doc/administration/get_started.md b/doc/administration/get_started.md index 01b555a2a06..44b09ef185a 100644 --- a/doc/administration/get_started.md +++ b/doc/administration/get_started.md @@ -37,8 +37,8 @@ Watch an overview of [groups and projects](https://www.youtube.com/watch?v=cqb2m Get started: - Create a [project](../user/project/working_with_projects.md#create-a-project). -- Create a [group](../user/group/index.md#create-a-group). -- [Add members](../user/group/index.md#add-users-to-a-group) to the group. +- Create a [group](../user/group/manage.md#create-a-group). +- [Add members](../user/group/manage.md#add-users-to-a-group) to the group. - Create a [subgroup](../user/group/subgroups/index.md#create-a-subgroup). - [Add members](../user/group/subgroups/index.md#subgroup-membership) to the subgroup. - Enable [external authorization control](../user/admin_area/settings/external_authorization.md#configuration). diff --git a/doc/administration/packages/container_registry.md b/doc/administration/packages/container_registry.md index 3bfb3ceca86..c5ece72b764 100644 --- a/doc/administration/packages/container_registry.md +++ b/doc/administration/packages/container_registry.md @@ -1388,7 +1388,7 @@ project or branch name. Special characters can include: - Trailing hyphen/dash - Double hyphen/dash -To get around this, you can [change the group path](../../user/group/index.md#change-a-groups-path), +To get around this, you can [change the group path](../../user/group/manage.md#change-a-groups-path), [change the project path](../../user/project/settings/index.md#rename-a-repository) or change the branch name. Another option is to create a [push rule](../../user/project/repository/push_rules.md) to prevent this at the instance level. diff --git a/doc/api/groups.md b/doc/api/groups.md index c51f23decb9..7e1ad9d69db 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -1435,7 +1435,7 @@ documentation. ## Share Groups with Groups -These endpoints create and delete links for sharing a group with another group. For more information, see the related discussion in the [GitLab Groups](../user/group/index.md#share-a-group-with-another-group) page. +These endpoints create and delete links for sharing a group with another group. For more information, see the related discussion in the [GitLab Groups](../user/group/manage.md#share-a-group-with-another-group) page. ### Create a link to share a group with another group diff --git a/doc/api/project_templates.md b/doc/api/project_templates.md index 4c0a1890729..7763087e759 100644 --- a/doc/api/project_templates.md +++ b/doc/api/project_templates.md @@ -20,7 +20,7 @@ It deprecates these endpoints, which are scheduled for removal in API version 5. In addition to templates common to the entire instance, project-specific templates are also available from this API endpoint. -Support is also available for [group-level file templates](../user/group/index.md#group-file-templates). **(PREMIUM)** +Support is also available for [group-level file templates](../user/group/manage.md#group-file-templates). **(PREMIUM)** ## Get all templates of a particular type diff --git a/doc/api/projects.md b/doc/api/projects.md index 8a8fe522b63..126507e413e 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -124,7 +124,7 @@ Example response: "parent_id": null, "avatar_url": null, "web_url": "https://gitlab.example.com/diaspora" - } + } }, { ... @@ -2102,7 +2102,7 @@ This endpoint: is applied if enabled. - From [GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) on [Premium or higher](https://about.gitlab.com/pricing/) tiers, group - administrators can [configure](../user/group/index.md#enable-delayed-project-deletion) + administrators can [configure](../user/group/manage.md#enable-delayed-project-deletion) projects within a group to be deleted after a delayed period. When enabled, actual deletion happens after the number of days specified in the [default deletion delay](../user/admin_area/settings/visibility_and_access_controls.md#deletion-protection). @@ -2110,7 +2110,7 @@ This endpoint: WARNING: The default behavior of [Delayed Project deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6 was changed to [Immediate deletion](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) -in GitLab 13.2, as discussed in [Enable delayed project deletion](../user/group/index.md#enable-delayed-project-deletion). +in GitLab 13.2, as discussed in [Enable delayed project deletion](../user/group/manage.md#enable-delayed-project-deletion). ```plaintext DELETE /projects/:id diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md index 35ee4f9dd33..0f1d1deb1dc 100644 --- a/doc/ci/environments/protected_environments.md +++ b/doc/ci/environments/protected_environments.md @@ -70,7 +70,7 @@ Alternatively, you can use the API to protect an environment: name: ${CI_JOB_NAME} ``` -1. Use the UI to [create a new group](../../user/group/index.md#create-a-group). +1. Use the UI to [create a new group](../../user/group/manage.md#create-a-group). For example, this group is called `protected-access-group` and has the group ID `9899826`. Note that the rest of the examples in these steps use this group. diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md index 52d6d4f7aee..a80bae67f04 100644 --- a/doc/subscriptions/gitlab_com/index.md +++ b/doc/subscriptions/gitlab_com/index.md @@ -33,9 +33,9 @@ To subscribe to GitLab SaaS: and decide which tier you want. 1. Create a user account for yourself by using the [sign up page](https://gitlab.com/users/sign_up). -1. Create a [group](../../user/group/index.md#create-a-group). Your subscription tier applies to the top-level group, its subgroups, and projects. +1. Create a [group](../../user/group/manage.md#create-a-group). Your subscription tier applies to the top-level group, its subgroups, and projects. 1. Create additional users and - [add them to the group](../../user/group/index.md#add-users-to-a-group). The users in this group, its subgroups, and projects can use + [add them to the group](../../user/group/manage.md#add-users-to-a-group). The users in this group, its subgroups, and projects can use the features of your subscription tier, and they consume a seat in your subscription. 1. On the left sidebar, select **Billing** and choose a tier. 1. Fill out the form to complete your purchase. @@ -73,7 +73,7 @@ subscription according to the maximum number of users assigned to the top-level add and remove users during the subscription period, as long as the total users at any given time doesn't exceed the subscription count. -A top-level group can be [changed](../../user/group/index.md#change-a-groups-path) like any other group. +A top-level group can be [changed](../../user/group/manage.md#change-a-groups-path) like any other group. Every user is included in seat usage, with the following exceptions: @@ -185,7 +185,7 @@ To remove a billable user from your subscription: 1. In the row for the user you want to remove, on the right side, select the ellipsis and **Remove user**. 1. Re-type the username and select **Remove user**. -If you add a member to a group by using the [share a group with another group](../../user/group/index.md#share-a-group-with-another-group) feature, you can't remove the member by using this method. Instead, you can either: +If you add a member to a group by using the [share a group with another group](../../user/group/manage.md#share-a-group-with-another-group) feature, you can't remove the member by using this method. Instead, you can either: - Remove the member from the shared group. You must be a group owner to do this. - From the group's membership page, remove access from the entire shared group. diff --git a/doc/user/admin_area/index.md b/doc/user/admin_area/index.md index 7ed064682cb..c5a345d0197 100644 --- a/doc/user/admin_area/index.md +++ b/doc/user/admin_area/index.md @@ -250,7 +250,7 @@ sort order is by **Last created**. To search for groups by name, enter your criteria in the search field. The group search is case insensitive, and applies partial matching. -To [Create a new group](../group/index.md#create-a-group) select **New group**. +To [Create a new group](../group/manage.md#create-a-group) select **New group**. ### Administering Topics diff --git a/doc/user/admin_area/merge_requests_approvals.md b/doc/user/admin_area/merge_requests_approvals.md index 526e8cd17da..e090d4e7f88 100644 --- a/doc/user/admin_area/merge_requests_approvals.md +++ b/doc/user/admin_area/merge_requests_approvals.md @@ -38,4 +38,4 @@ Merge request approval settings that can be set at an instance level are: See also the following, which are affected by instance-level rules: - [Project merge request approval rules](../project/merge_requests/approvals/index.md). -- [Group merge request approval settings](../group/index.md#group-merge-request-approval-settings) available in GitLab 13.9 and later. +- [Group merge request approval settings](../group/manage.md#group-merge-request-approval-settings) available in GitLab 13.9 and later. diff --git a/doc/user/admin_area/settings/visibility_and_access_controls.md b/doc/user/admin_area/settings/visibility_and_access_controls.md index 8a9db68b34f..15cd07b3083 100644 --- a/doc/user/admin_area/settings/visibility_and_access_controls.md +++ b/doc/user/admin_area/settings/visibility_and_access_controls.md @@ -20,7 +20,7 @@ To access the visibility and access control options: ## Define which roles can create projects Instance-level protections for project creation define which roles can -[add projects to a group](../../group/index.md#specify-who-can-add-projects-to-a-group) +[add projects to a group](../../group/manage.md#specify-who-can-add-projects-to-a-group) on the instance. To alter which roles have permission to create projects: 1. Sign in to GitLab as a user with Administrator access level. diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md index 1f34d182718..0eb6d853531 100644 --- a/doc/user/discussions/index.md +++ b/doc/user/discussions/index.md @@ -47,7 +47,7 @@ in a different color. Avoid mentioning `@all` in issues and merge requests, because it sends an email notification to all the members of that project's group. This might be interpreted as spam. Notifications and mentions can be disabled in -[a group's settings](../group/index.md#disable-email-notifications). +[a group's settings](../group/manage.md#disable-email-notifications). ## Add a comment to a merge request diff diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md index 529b81e2645..deaaf808271 100644 --- a/doc/user/gitlab_com/index.md +++ b/doc/user/gitlab_com/index.md @@ -96,7 +96,7 @@ Projects are permanently deleted after a seven-day delay. If you are on: -- Premium tier and above, you can disable this by changing the [group setting](../group/index.md#enable-delayed-project-deletion). +- Premium tier and above, you can disable this by changing the [group setting](../group/manage.md#enable-delayed-project-deletion). - Free tier, you cannot disable this setting or restore projects. ## Inactive project deletion diff --git a/doc/user/group/custom_project_templates.md b/doc/user/group/custom_project_templates.md index 6755bf9ffb9..43587dd54ef 100644 --- a/doc/user/group/custom_project_templates.md +++ b/doc/user/group/custom_project_templates.md @@ -30,7 +30,7 @@ To set up custom project templates in a group, add the subgroup that contains th project templates to the group settings: 1. In the group, create a [subgroup](subgroups/index.md). -1. [Add projects to the new subgroup](index.md#add-projects-to-a-group) as your templates. +1. [Add projects to the new subgroup](manage.md#add-projects-to-a-group) as your templates. 1. In the left menu for the group, select **Settings > General**. 1. Expand **Custom project templates** and select the subgroup. diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 56d1569c908..420e06c3678 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -18,17 +18,9 @@ You can use groups to communicate with all of the members of the group at once. For larger organizations, you can also create [subgroups](subgroups/index.md). -## View groups +For more information about creating and managing your groups, see [Manage groups](manage.md). -To view groups: - -1. On the top bar, select **Menu > Groups**. -1. Select **Your Groups**. All groups you are a member of are displayed. -1. To view a list of public groups, select **Explore public groups**. - -You can also view groups by namespace. - -### Group visibility +## Group visibility Like projects, a group can be configured to limit the visibility of it to: @@ -43,74 +35,18 @@ empty for anonymous users. The group page has a visibility level icon. Administrator users cannot create a subgroup or project with a higher visibility level than that of the immediate parent group. -### Namespaces - -In GitLab, a *namespace* organizes related projects together. -GitLab has two types of namespaces: - -- A *personal* namespace, which is based on your username. Projects under a personal namespace must be configured one at a time. -- A *group* or *subgroup* namespace. In these namespaces, you can manage multiple projects at once. - -To determine whether you're viewing a group or personal namespace, you can view the URL. For example: - -| Namespace for | URL | Namespace | -| ------------- | --- | --------- | -| A user named `alex`. | `https://gitlab.example.com/alex` | `alex` | -| A group named `alex-team`. | `https://gitlab.example.com/alex-team` | `alex-team` | -| A group named `alex-team` with a subgroup named `marketing`. | `https://gitlab.example.com/alex-team/marketing` | `alex-team/marketing` | - -## Create a group - -To create a group: - -1. On the top bar, either: - - Select **Menu > Groups**, and on the right, select **Create group**. - - To the left of the search box, select the plus sign and then **New group**. -1. Select **Create group**. -1. Enter a name for the group in **Group name**. For a list of words that cannot be used as group names, see - [reserved names](../reserved_names.md). -1. Enter a path for the group in **Group URL**, which is used for the [namespace](#namespaces). -1. Choose the [visibility level](../public_access.md). -1. Personalize your GitLab experience by answering the following questions: - - What is your role? - - Who will be using this group? - - What will you use this group for? -1. Invite GitLab members or other users to join the group. - -<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> -For details about groups, watch [GitLab Namespaces (users, groups and subgroups)](https://youtu.be/r0sJgjR2f5A). - -## Add users to a group - -You can give a user access to all projects in a group. +## Namespaces -1. On the top bar, select **Menu > Groups** and find your group. -1. On the left sidebar, select **Group information > Members**. -1. Select **Invite members**. -1. Fill in the fields. - - The role applies to all projects in the group. [Learn more about permissions](../permissions.md). - - On the **Access expiration date**, the user can no longer access projects in the group. -1. Select **Invite**. - -Members that are not automatically added are displayed on the **Invited** tab. -Users can be on this tab because they: - -- Have not yet accepted the invitation. -- Are waiting for [approval from an administrator](../admin_area/moderate_users.md). -- [Exceed the group user cap](#user-cap-for-groups). - -## Request access to a group - -As a user, you can request to be a member of a group, if an administrator allows it. - -1. On the top bar, select **Menu > Groups** and find your group. -1. Under the group name, select **Request Access**. +In GitLab, a namespace is a unique name for a user, a group, or subgroup under +which a project can be created. -As many as ten of the most-recently-active group owners receive an email with your request. -Any group owner can approve or decline the request. +For example, consider a user named Alex: -If you change your mind before your request is approved, select -**Withdraw Access Request**. +| GitLab URL | Namespace | +| ---------- | --------- | +| Alex creates an account with the username `alex`: `https://gitlab.example.com/alex`. | The namespace in this case is `alex`. | +| Alex creates a group for their team with the group name `alex-team`. The group and its projects are available at: `https://gitlab.example.com/alex-team`. | The namespace in this case is `alex-team`. | +| Alex creates a subgroup of `alex-team` with the subgroup name `marketing`. The subgroup and its projects are available at: `https://gitlab.example.com/alex-team/marketing`. | The namespace in this case is `alex-team/marketing`. | ## Prevent users from requesting access to a group @@ -125,75 +61,6 @@ your group. 1. Clear the **Allow users to request access** checkbox. 1. Select **Save changes**. -## Change the owner of a group - -You can change the owner of a group. Each group must always have at least one -member with the Owner role. - -- As an administrator: - 1. Go to the group and from the left menu, select **Group information > Members**. - 1. Give a different member the **Owner** role. - 1. Refresh the page. You can now remove the **Owner** role from the original owner. -- As the current group's owner: - 1. Go to the group and from the left menu, select **Group information > Members**. - 1. Give a different member the **Owner** role. - 1. Have the new owner sign in and remove the **Owner** role from you. - -## Remove a member from the group - -Prerequisites: - -- You must have the Owner role. -- The member must have direct membership in the group. If - membership is inherited from a parent group, then the member can be removed - from the parent group only. - -To remove a member from a group: - -1. Go to the group. -1. From the left menu, select **Group information > Members**. -1. Next to the member you want to remove, select **Delete**. -1. Optional. On the **Remove member** confirmation box, select the - **Also unassign this user from linked issues and merge requests** checkbox. -1. Select **Remove member**. - -## Filter and sort members in a group - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6. -> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/228675) in GitLab 13.7. -> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289911) in GitLab 13.8. - -To find members in a group, you can sort, filter, or search. - -### Filter a group - -Filter a group to find members. By default, all members in the group and subgroups are displayed. - -1. Go to the group and select **Group information > Members**. -1. Above the list of members, in the **Filter members** box, enter filter criteria. - - To view members in the group only, select **Membership = Direct**. - - To view members of the group and its subgroups, select **Membership = Inherited**. - - To view members with two-factor authentication enabled or disabled, select **2FA = Enabled** or **Disabled**. - - [In GitLab 14.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/349887), to view GitLab users created by [SAML SSO](saml_sso/index.md) or [SCIM provisioning](saml_sso/scim_setup.md) select **Enterprise = true**. - -### Search a group - -You can search for members by name, username, or email. - -1. Go to the group and select **Group information > Members**. -1. Above the list of members, in the **Filter members** box, enter search criteria. -1. To the right of the **Filter members** box, select the magnifying glass (**{search}**). - -### Sort members in a group - -You can sort members by **Account**, **Access granted**, **Max role**, or **Last sign-in**. - -1. Go to the group and select **Group information > Members**. -1. Above the list of members, on the top right, from the **Account** list, select - the criteria to filter by. -1. To switch the sort between ascending and descending, to the right of the **Account** list, select the - arrow (**{sort-lowest}** or **{sort-highest}**). - ## Mention a group in an issue or merge request When you mention a group in a comment, every member of the group gets a to-do item @@ -206,100 +73,6 @@ added to their To-do list. A to-do item is created for all the group and subgroup members. -## Change the default branch protection of a group - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9. -> - [Settings moved and renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/340403) in GitLab 14.9. - -By default, every group inherits the branch protection set at the global level. - -To change this setting for a specific group, see [group level default branch protection](../project/repository/branches/default.md#group-level-default-branch-protection). - -To change this setting globally, see [initial default branch protection](../project/repository/branches/default.md#instance-level-default-branch-protection). - -NOTE: -In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../project/repository/branches/default.md#prevent-overrides-of-default-branch-protection). - -## Add projects to a group - -There are two different ways to add a new project to a group: - -- Select a group, and then select **New project**. You can then continue [creating your project](../../user/project/working_with_projects.md#create-a-project). -- While you are creating a project, select a group from the dropdown list. - -  - -### Specify who can add projects to a group - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2534) in GitLab 10.5. -> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25975) from GitLab Premium to GitLab Free in 11.10. - -By default, users with at least the Developer role can create projects under a group. - -To change this setting for a specific group: - -1. On the top bar, select **Menu > Groups**. -1. Select **Your Groups**. -1. Find the group and select it. -1. From the left menu, select **Settings > General**. -1. Expand the **Permissions and group features** section. -1. Select the desired option in the **Roles allowed to create projects** dropdown list. -1. Select **Save changes**. - -To change this setting globally, see [Default project creation protection](../admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects). - -## Group activity analytics **(PREMIUM)** - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207164) in GitLab 12.10 as a [Beta feature](../../policy/alpha-beta-support.md#beta-features). - -For a group, you can view how many merge requests, issues, and members were created in the last 90 days. - -These Group Activity Analytics can be enabled with the `group_activity_analytics` [feature flag](../../development/feature_flags/index.md#enabling-a-feature-flag-locally-in-development). - - - -Changes to [group wikis](../project/wiki/group.md) do not appear in group activity analytics. - -### View group activity - -You can view the most recent actions taken in a group, either in your browser or in an RSS feed: - -1. On the top bar, select **Menu > Groups**. -1. Select **Your Groups**. -1. Find the group and select it. -1. On the left sidebar, select **Group information > Activity**. - -To view the activity feed in Atom format, select the -**RSS** (**{rss}**) icon. - -## Share a group with another group - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7. -> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 from a form to a modal window [with a flag](../feature_flags.md). Disabled by default. -> - Modal window [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8. -> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) in GitLab 14.9. - [Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed. - -Similar to how you [share a project with a group](../project/members/share_project_with_groups.md), -you can share a group with another group. To invite a group, you must be a member of it. Members get direct access -to the shared group. This includes members who inherited group membership from a parent group. - -To share a given group, for example, `Frontend` with another group, for example, -`Engineering`: - -1. Go to the `Frontend` group. -1. On the left sidebar, select **Group information > Members**. -1. Select **Invite a group**. -1. In the **Select a group to invite** list, select `Engineering`. -1. Select a [role](../permissions.md) as maximum access level. -1. Select **Invite**. - -After sharing the `Frontend` group with the `Engineering` group: - -- The **Groups** tab lists the `Engineering` group. -- The **Groups** tab lists a group regardless of whether it is a public or private group. -- All members of the `Engineering` group have access to the `Frontend` group. The same access levels of the members apply up to the maximum access level selected when sharing the group. - ## Manage group memberships via LDAP **(PREMIUM SELF)** Group syncing allows LDAP groups to be mapped to GitLab groups. This provides more control over per-group user management. To configure group syncing, edit the `group_base` **DN** (`'OU=Global Groups,OU=GitLab INT,DC=GitLab,DC=org'`). This **OU** contains all groups that are associated with GitLab groups. @@ -345,118 +118,6 @@ LDAP user permissions can be manually overridden by an administrator. To overrid Now you can edit the user's permissions from the **Members** page. -## Transfer a group - -You can transfer groups in the following ways: - -- Transfer a subgroup to a new parent group. -- Convert a top-level group into a subgroup by transferring it to the desired group. -- Convert a subgroup into a top-level group by transferring it out of its current group. - -When transferring groups, note: - -- Changing a group's parent can have unintended side effects. See [what happens when a repository path changes](../project/repository/index.md#what-happens-when-a-repository-path-changes). -- You can only transfer groups to groups you manage. -- You must update your local repositories to point to the new location. -- If the immediate parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects change to match the new parent group's visibility. -- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this leaves the group without an owner. In this case, the user transferring the group becomes the group's owner. -- Transfers fail if [packages](../packages/index.md) exist in any of the projects in the group, or in any of its subgroups. - -## Change a group's path - -Changing a group's path (group URL) can have unintended side effects. Read -[how redirects behave](../project/repository/index.md#what-happens-when-a-repository-path-changes) -before you proceed. - -If you are changing the path so it can be claimed by another group or user, -you must rename the group too. Both names and paths must -be unique. - -To retain ownership of the original namespace and protect the URL redirects, -create a new group and transfer projects to it instead. - -To change your group path (group URL): - -1. Go to your group's **Settings > General** page. -1. Expand the **Advanced** section. -1. Under **Change group URL**, enter a new name. -1. Select **Change group URL**. - -WARNING: -It is not possible to rename a namespace if it contains a -project with [Container Registry](../packages/container_registry/index.md) tags, -because the project cannot be moved. - -## Use a custom name for the initial branch - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43290) in GitLab 13.6. - -When you create a new project in GitLab, a default branch is created with the -first push. The group owner can -[customize the initial branch](../project/repository/branches/default.md#group-level-custom-initial-branch-name) -for the group's projects to meet your group's needs. - -## Remove a group - -To remove a group and its contents: - -1. On the top bar, select **Menu > Groups** and find your group. -1. On the left sidebar, select **Settings > General**. -1. Expand the **Advanced** section. -1. In the **Remove group** section, select **Remove group**. -1. Type the group name. -1. Select **Confirm**. - -A group can also be removed from the groups dashboard: - -1. On the top bar, select **Menu > Groups**. -1. Select **Your Groups**. -1. Select (**{ellipsis_v}**) for the group you want to delete. -1. Select **Delete**. -1. In the Remove group section, select **Remove group**. -1. Type the group name. -1. Select **Confirm**. - -This action removes the group. It also adds a background job to delete all projects in the group. - -Specifically: - -- In [GitLab 12.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [GitLab Premium](https://about.gitlab.com/pricing/premium/) or higher tiers, this action adds a background job to mark a group for deletion. By default, the job schedules the deletion 7 days in the future. You can modify this waiting period through the [instance settings](../admin_area/settings/visibility_and_access_controls.md#deletion-protection). -- In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the -deletion happens, the job is cancelled, and the group is no longer scheduled for deletion. - -## Remove a group immediately **(PREMIUM)** - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336985) in GitLab 14.2. - -If you don't want to wait, you can remove a group immediately. - -Prerequisites: - -- You must have at least the Owner role for a group. -- You have [marked the group for deletion](#remove-a-group). - -To immediately remove a group marked for deletion: - -1. On the top bar, select **Menu > Groups** and find your group. -1. On the left sidebar, select **Settings > General**. -1. Expand **Advanced**. -1. In the "Permanently remove group" section, select **Remove group**. -1. Confirm the action when asked to. - -Your group, its subgroups, projects, and all related resources, including issues and merge requests, -are deleted. - -## Restore a group **(PREMIUM)** - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33257) in GitLab 12.8. - -To restore a group that is marked for deletion: - -1. Go to your group's **Settings > General** page. -1. Expand the **Path, transfer, remove** section. -1. In the Restore group section, select **Restore group**. - ## Prevent group sharing outside the group hierarchy You can configure a top-level group so its subgroups and projects @@ -481,7 +142,7 @@ To prevent sharing outside of the group's hierarchy: 1. On the top bar, select **Menu > Groups** and find your group. 1. On the left sidebar, select **Settings > General**. 1. Expand **Permissions and group features**. -1. Select **Members cannot invite groups outside of `<group_name>` and its subgroups**. +1. Select **Prevent members from sending invitations to groups outside of `<group_name>` and its subgroups**. 1. Select **Save changes**. ## Prevent a project from being shared with groups @@ -493,82 +154,12 @@ To prevent a project from being shared with other groups: 1. Go to the group's **Settings > General** page. 1. Expand the **Permissions and group features** section. -1. Select **Projects in `<group_name>` cannot be shared with other groups**. +1. Select **Prevent sharing a project in `<group_name>` with other groups**. 1. Select **Save changes**. This setting applies to all subgroups unless overridden by a group owner. Groups already added to a project lose access when the setting is enabled. -## User cap for groups - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330027) in GitLab 14.7. - -FLAG: -On self-managed GitLab, this feature is not available. On GitLab.com, this feature is available for some groups. -This feature is not ready for production use. - -When the number of billable members reaches the user cap, new users can't be added to the group -without being approved by the group owner. - -Groups with the user cap feature enabled have [group sharing](#share-a-group-with-another-group) -disabled for the group and its subgroups. - -### Specify a user cap for a group - -Prerequisite: - -- You must be assigned the Owner role) for the group. - -To specify a user cap: - -1. On the top bar, select **Menu > Groups** and find your group. - You can set a cap on the top-level group only. -1. On the left sidebar, select **Settings > General**. -1. Expand **Permissions and group features**. -1. In the **User cap** box, enter the desired number of users. -1. Select **Save changes**. - -If you already have more users in the group than the user cap value, users -are not removed. However, you can't add more without approval. - -Increasing the user cap does not approve pending members. - -### Remove the user cap for a group - -You can remove the user cap, so there is no limit on the number of members you can add to a group. - -Prerequisite: - -- You must be assigned the Owner role) for the group. - -To remove the user cap: - -1. On the top bar, select **Menu > Groups** and find your group. -1. On the left sidebar, select **Settings > General**. -1. Expand **Permissions and group features**. -1. In the **User cap** box, delete the value. -1. Select **Save changes**. - -Decreasing the user cap does not approve pending members. - -### Approve pending members for a group - -When the number of billable users reaches the user cap, any new member is put in a pending state -and must be approved. - -Pending members do not count as billable. Members count as billable only after they have been approved and are no longer in a pending state. - -Prerequisite: - -- You must be assigned the Owner role) for the group. - -To approve members that are pending because they've exceeded the user cap: - -1. On the top bar, select **Menu > Groups** and find your group. -1. On the left sidebar, select **Settings > Usage Quotas**. -1. On the **Seats** tab, under the alert, select **View pending approvals**. -1. For each member you want to approve, select **Approve**. - ## Prevent members from being added to projects in a group **(PREMIUM)** As a group owner, you can prevent any new project membership for all @@ -585,7 +176,7 @@ To prevent members from being added to projects in a group: 1. Go to the group's **Settings > General** page. 1. Expand the **Permissions and group features** section. -1. Under **Membership**, select **Users cannot be added to projects in this group**. +1. Under **Membership**, select **Prevent adding new members to projects within this group**. 1. Select **Save changes**. All users who previously had permissions can no longer add members to a group. @@ -611,14 +202,15 @@ To ensure only people from your organization can access particular resources, you can restrict access to groups by IP address. This group-level setting applies to: -- The GitLab UI, including subgroups, projects, issues, and pages. +- The GitLab UI, including subgroups, projects, and issues. - [In GitLab 12.3 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/12874), the API. -- Using Git over SSH on GitLab.com. ### Security implications You should consider some security implications before configuring IP address restrictions. +- Restricting HTTP traffic on GitLab.com with IP address restrictions causes SSH requests (including Git operations over + SSH) to fail. For more information, see [the relevant issue](https://gitlab.com/gitlab-org/gitlab/-/issues/271673). - Administrators and group owners can access group settings from any IP address, regardless of IP restriction. However: - Groups owners cannot access projects belonging to the group when accessing from a disallowed IP address. - Administrators can access projects belonging to the group when accessing from a disallowed IP address. @@ -631,8 +223,6 @@ You should consider some security implications before configuring IP address res restricted IP address, the IP restriction prevents code from being cloned. - Users may still see some events from the IP restricted groups and projects on their dashboard. Activity may include push, merge, issue, or comment events. -- IP access restrictions for Git operations via SSH are supported only on GitLab SaaS. - IP access restrictions applied to self-managed instances block SSH completely. ### Restrict group access by IP address @@ -640,7 +230,7 @@ To restrict group access by IP address: 1. Go to the group's **Settings > General** page. 1. Expand the **Permissions and group features** section. -1. In the **Restrict access by IP address** field, enter IPv4 or IPv6 address ranges in CIDR notation. +1. In the **Allow access to the following IP addresses** field, enter IPv4 or IPv6 address ranges in CIDR notation. 1. Select **Save changes**. In self-managed installations of GitLab 15.1 and later, you can also configure @@ -652,7 +242,6 @@ at the group level. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2. > - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1. > - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2. -> - Support for restricting group memberships to groups with a subset of the allowed email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/354791) in GitLab 15.0.1 You can prevent users with email addresses in specific domains from being added to a group and its projects. @@ -673,127 +262,6 @@ The most popular public email domains cannot be restricted, such as: - `hotmail.com`, `hotmail.co.uk`, `hotmail.fr` - `msn.com`, `live.com`, `outlook.com` -When you share a group, both the source and target namespaces must allow the domains of the members' email addresses. - -## Restrict Git access protocols - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/365601) in GitLab 15.1 [with a flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. Disabled by default. - -FLAG: -On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to -[enable the feature flag](../../administration/feature_flags.md) named `group_level_git_protocol_control`. On GitLab.com, -this feature is available. - -You can set the permitted protocols used to access a group's repositories to either SSH, HTTPS, or both. This setting -is disabled when the [instance setting](../admin_area/settings/visibility_and_access_controls.md#configure-enabled-git-access-protocols) is -configured by an administrator. - -To change the permitted Git access protocols for a group: - -1. Go to the group's **Settings > General** page. -1. Expand the **Permissions and group features** section. -1. Choose the permitted protocols from **Enabled Git access protocols**. -1. Select **Save changes**. - -## Group file templates **(PREMIUM)** - -Use group file templates to share a set of templates for common file -types with every project in a group. It is analogous to the -[instance template repository](../admin_area/settings/instance_template_repository.md). -The selected project should follow the same naming conventions as -are documented on that page. - -You can only choose projects in the group as the template source. -This includes projects shared with the group, but it **excludes** projects in -subgroups or parent groups of the group being configured. - -You can configure this feature for both subgroups and immediate parent groups. A project -in a subgroup has access to the templates for that subgroup, as well as -any immediate parent groups. - -To learn how to create templates for issues and merge requests, see -[Description templates](../project/description_templates.md). - -Define project templates at a group level by setting a group as the template source. -[Learn more about group-level project templates](custom_project_templates.md). - -### Enable group file template **(PREMIUM)** - -To enable group file templates: - -1. Go to the group's **Settings > General** page. -1. Expand the **Templates** section. -1. Choose a project to act as the template repository. -1. Select **Save changes**. - -## Disable email notifications - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23585) in GitLab 12.2. - -You can disable all email notifications related to the group, which includes its subgroups and projects. - -To disable email notifications: - -1. Go to the group's **Settings > General** page. -1. Expand the **Permissions and group features** section. -1. Select **Email notifications are disabled**. -1. Select **Save changes**. - -## Disable group mentions - -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21301) in GitLab 12.6. - -You can prevent users from being added to a conversation and getting notified when -anyone [mentions a group](../discussions/index.md#mentions) -in which those users are members. - -Groups with disabled mentions are visualized accordingly in the autocompletion dropdown list. - -This is particularly helpful for groups with a large number of users. - -To disable group mentions: - -1. Go to the group's **Settings > General** page. -1. Expand the **Permissions and group features** section. -1. Select **Group mentions are disabled**. -1. Select **Save changes**. - -## Enable delayed project deletion **(PREMIUM)** - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2. -> - [Inheritance and enforcement added](https://gitlab.com/gitlab-org/gitlab/-/issues/321724) in GitLab 13.11. -> - [Instance setting to enable by default added](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2. -> - [Instance setting is inherited and enforced when disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) in GitLab 15.1. -> - [User interface changed](https://gitlab.com/gitlab-org/gitlab/-/issues/352961) in GitLab 15.1. - -[Delayed project deletion](../project/settings/index.md#delayed-project-deletion) is locked and disabled unless the instance-level settings for -[deletion protection](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) are enabled for either groups only or groups and projects. -When enabled on groups, projects in the group are deleted after a period of delay. During this period, projects are in a read-only state and can be restored. -The default period is seven days but [is configurable at the instance level](../admin_area/settings/visibility_and_access_controls.md#retention-period). - -On self-managed GitLab, projects are deleted immediately by default. -In GitLab 14.2 and later, an administrator can -[change the default setting](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) -for projects in newly-created groups. - -On GitLab.com, see the [GitLab.com settings page](../gitlab_com/index.md#delayed-project-deletion) for -the default setting. - -To enable delayed deletion of projects in a group: - -1. Go to the group's **Settings > General** page. -1. Expand the **Permissions and group features** section. -1. Scroll to: - - (GitLab 15.1 and later) **Deletion protection** and select **Keep deleted projects**. - - (GitLab 15.0 and earlier) **Enable delayed project deletion** and tick the checkbox. -1. Optional. To prevent subgroups from changing this setting, select: - - (GitLab 15.1 and later), **Enforce deletion protection for all subgroups** - - (GitLab 15.0 and earlier), **Enforce for all subgroups**. -1. Select **Save changes**. - -NOTE: -In GitLab 13.11 and above the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md) inheritance can be overridden, unless enforced by an ancestor. - ## Prevent project forking outside group **(PREMIUM)** > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216987) in GitLab 13.3. @@ -834,25 +302,6 @@ The group's new subgroups have push rules set for them based on either: - The closest parent group with push rules defined. - Push rules set at the instance level, if no parent groups have push rules defined. -## Group merge request approval settings **(PREMIUM)** - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285458) in GitLab 13.9. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../administration/feature_flags.md), disabled by default. -> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.5. -> - [Feature flag `group_merge_request_approval_settings_feature_flag`](https://gitlab.com/gitlab-org/gitlab/-/issues/343872) removed in GitLab 14.9. - -Group approval settings manage [project merge request approval settings](../project/merge_requests/approvals/settings.md) -at the top-level group level. These settings [cascade to all projects](../project/merge_requests/approvals/settings.md#settings-cascading) -that belong to the group. - -To view the merge request approval settings for a group: - -1. Go to the top-level group's **Settings > General** page. -1. Expand the **Merge request approvals** section. -1. Select the settings you want. -1. Select **Save changes**. - -Support for group-level settings for merge request approval rules is tracked in this [epic](https://gitlab.com/groups/gitlab-org/-/epics/4367). - ## Related topics - [Group wikis](../project/wiki/index.md) @@ -891,14 +340,3 @@ If a user sees a 404 when they would normally expect access, and the problem is In viewing the log entries, compare the `remote.ip` with the list of [allowed IPs](#group-access-restriction-by-ip-address) for the group. - -### Validation errors on namespaces and groups - -[GitLab 14.4 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70365) performs -the following checks when creating or updating namespaces or groups: - -- Namespaces must not have parents. -- Group parents must be groups and not namespaces. - -In the unlikely event that you see these errors in your GitLab installation, -[contact Support](https://about.gitlab.com/support/) so that we can improve this validation. diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md index c52a20bf5f5..453b9b976af 100644 --- a/doc/user/group/manage.md +++ b/doc/user/group/manage.md @@ -9,3 +9,532 @@ info: To determine the technical writer assigned to the Stage/Group associated w Use Groups to manage one or more related projects at the same time. For instructions on how to view, create, and manage groups, see [Groups](index.md). + +## Create a group + +To create a group: + +1. On the top bar, either: + - Select **Menu > Groups**, and on the right, select **Create group**. + - To the left of the search box, select the plus sign and then **New group**. +1. Select **Create group**. +1. Enter a name for the group in **Group name**. For a list of words that cannot be used as group names, see + [reserved names](../reserved_names.md). +1. Enter a path for the group in **Group URL**, which is used for the [namespace](index.md#namespaces). +1. Choose the [visibility level](../public_access.md). +1. Personalize your GitLab experience by answering the following questions: + - What is your role? + - Who will be using this group? + - What will you use this group for? +1. Invite GitLab members or other users to join the group. + +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For details about groups, watch [GitLab Namespaces (users, groups and subgroups)](https://youtu.be/r0sJgjR2f5A). + +## Remove a group + +To remove a group and its contents: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > General**. +1. Expand the **Advanced** section. +1. In the **Remove group** section, select **Remove group**. +1. Type the group name. +1. Select **Confirm**. + +A group can also be removed from the groups dashboard: + +1. On the top bar, select **Menu > Groups**. +1. Select **Your Groups**. +1. Select (**{ellipsis_v}**) for the group you want to delete. +1. Select **Delete**. +1. In the Remove group section, select **Remove group**. +1. Type the group name. +1. Select **Confirm**. + +This action removes the group. It also adds a background job to delete all projects in the group. + +Specifically: + +- In [GitLab 12.8 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/33257), on [GitLab Premium](https://about.gitlab.com/pricing/premium/) or higher tiers, this action adds a background job to mark a group for deletion. By default, the job schedules the deletion 7 days in the future. You can modify this waiting period through the [instance settings](../admin_area/settings/visibility_and_access_controls.md#deletion-protection). +- In [GitLab 13.6 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/39504), if the user who sets up the deletion is removed from the group before the +deletion happens, the job is cancelled, and the group is no longer scheduled for deletion. + +## Remove a group immediately **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/336985) in GitLab 14.2. + +If you don't want to wait, you can remove a group immediately. + +Prerequisites: + +- You must have at least the Owner role for a group. +- You have [marked the group for deletion](#remove-a-group). + +To immediately remove a group marked for deletion: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > General**. +1. Expand **Advanced**. +1. In the "Permanently remove group" section, select **Remove group**. +1. Confirm the action when asked to. + +Your group, its subgroups, projects, and all related resources, including issues and merge requests, +are deleted. + +## Restore a group **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33257) in GitLab 12.8. + +To restore a group that is marked for deletion: + +1. Go to your group's **Settings > General** page. +1. Expand the **Path, transfer, remove** section. +1. In the Restore group section, select **Restore group**. + +## Request access to a group + +As a user, you can request to be a member of a group, if an administrator allows it. + +1. On the top bar, select **Menu > Groups** and find your group. +1. Under the group name, select **Request Access**. + +As many as ten of the most-recently-active group owners receive an email with your request. +Any group owner can approve or decline the request. + +If you change your mind before your request is approved, select +**Withdraw Access Request**. + +## Filter and sort members in a group + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21727) in GitLab 12.6. +> - [Improved](https://gitlab.com/gitlab-org/gitlab/-/issues/228675) in GitLab 13.7. +> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/289911) in GitLab 13.8. + +To find members in a group, you can sort, filter, or search. + +### Filter a group + +Filter a group to find members. By default, all members in the group and subgroups are displayed. + +1. Go to the group and select **Group information > Members**. +1. Above the list of members, in the **Filter members** box, enter filter criteria. + - To view members in the group only, select **Membership = Direct**. + - To view members of the group and its subgroups, select **Membership = Inherited**. + - To view members with two-factor authentication enabled or disabled, select **2FA = Enabled** or **Disabled**. + - [In GitLab 14.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/349887), to view GitLab users created by [SAML SSO](saml_sso/index.md) or [SCIM provisioning](saml_sso/scim_setup.md) select **Enterprise = true**. + +### Search a group + +You can search for members by name, username, or email. + +1. Go to the group and select **Group information > Members**. +1. Above the list of members, in the **Filter members** box, enter search criteria. +1. To the right of the **Filter members** box, select the magnifying glass (**{search}**). + +### Sort members in a group + +You can sort members by **Account**, **Access granted**, **Max role**, or **Last sign-in**. + +1. Go to the group and select **Group information > Members**. +1. Above the list of members, on the top right, from the **Account** list, select + the criteria to filter by. +1. To switch the sort between ascending and descending, to the right of the **Account** list, select the + arrow (**{sort-lowest}** or **{sort-highest}**). + +## Add users to a group + +You can give a user access to all projects in a group. + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Group information > Members**. +1. Select **Invite members**. +1. Fill in the fields. + - The role applies to all projects in the group. [Learn more about permissions](../permissions.md). + - On the **Access expiration date**, the user can no longer access projects in the group. +1. Select **Invite**. + +Members that are not automatically added are displayed on the **Invited** tab. +Users can be on this tab because they: + +- Have not yet accepted the invitation. +- Are waiting for [approval from an administrator](../admin_area/moderate_users.md). +- [Exceed the group user cap](#user-cap-for-groups). + +## Remove a member from the group + +Prerequisites: + +- You must have the Owner role. +- The member must have direct membership in the group. If + membership is inherited from a parent group, then the member can be removed + from the parent group only. + +To remove a member from a group: + +1. Go to the group. +1. From the left menu, select **Group information > Members**. +1. Next to the member you want to remove, select **Delete**. +1. Optional. On the **Remove member** confirmation box, select the + **Also unassign this user from linked issues and merge requests** checkbox. +1. Select **Remove member**. + +## Add projects to a group + +There are two different ways to add a new project to a group: + +- Select a group, and then select **New project**. You can then continue [creating your project](../../user/project/working_with_projects.md#create-a-project). +- While you are creating a project, select a group from the dropdown list. + +  + +### Specify who can add projects to a group + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/2534) in GitLab 10.5. +> - [Moved](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25975) from GitLab Premium to GitLab Free in 11.10. + +By default, users with at least the Developer role can create projects under a group. + +To change this setting for a specific group: + +1. On the top bar, select **Menu > Groups**. +1. Select **Your Groups**. +1. Find the group and select it. +1. From the left menu, select **Settings > General**. +1. Expand the **Permissions and group features** section. +1. Select the desired option in the **Allowed to create projects** dropdown list. +1. Select **Save changes**. + +To change this setting globally, see [Default project creation protection](../admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects). + +## Change the owner of a group + +You can change the owner of a group. Each group must always have at least one +member with the Owner role. + +- As an administrator: + 1. Go to the group and from the left menu, select **Group information > Members**. + 1. Give a different member the **Owner** role. + 1. Refresh the page. You can now remove the **Owner** role from the original owner. +- As the current group's owner: + 1. Go to the group and from the left menu, select **Group information > Members**. + 1. Give a different member the **Owner** role. + 1. Have the new owner sign in and remove the **Owner** role from you. + +## Change a group's path + +Changing a group's path (group URL) can have unintended side effects. Read +[how redirects behave](../project/repository/index.md#what-happens-when-a-repository-path-changes) +before you proceed. + +If you are changing the path so it can be claimed by another group or user, +you must rename the group too. Both names and paths must +be unique. + +To retain ownership of the original namespace and protect the URL redirects, +create a new group and transfer projects to it instead. + +To change your group path (group URL): + +1. Go to your group's **Settings > General** page. +1. Expand the **Advanced** section. +1. Under **Change group URL**, enter a new name. +1. Select **Change group URL**. + +WARNING: +It is not possible to rename a namespace if it contains a +project with [Container Registry](../packages/container_registry/index.md) tags, +because the project cannot be moved. + +## Change the default branch protection of a group + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7583) in GitLab 12.9. +> - [Settings moved and renamed](https://gitlab.com/gitlab-org/gitlab/-/issues/340403) in GitLab 14.9. + +By default, every group inherits the branch protection set at the global level. + +To change this setting for a specific group, see [group level default branch protection](../project/repository/branches/default.md#group-level-default-branch-protection). + +To change this setting globally, see [initial default branch protection](../project/repository/branches/default.md#instance-level-default-branch-protection). + +NOTE: +In [GitLab Premium or higher](https://about.gitlab.com/pricing/), GitLab administrators can choose to [disable group owners from updating the default branch protection](../project/repository/branches/default.md#prevent-overrides-of-default-branch-protection). + +## Use a custom name for the initial branch + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43290) in GitLab 13.6. + +When you create a new project in GitLab, a default branch is created with the +first push. The group owner can +[customize the initial branch](../project/repository/branches/default.md#group-level-custom-initial-branch-name) +for the group's projects to meet your group's needs. + +## Share a group with another group + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/18328) in GitLab 12.7. +> - [Changed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 13.11 from a form to a modal window [with a flag](../feature_flags.md). Disabled by default. +> - Modal window [enabled on GitLab.com and self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/247208) in GitLab 14.8. +> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) in GitLab 14.9. + [Feature flag `invite_members_group_modal`](https://gitlab.com/gitlab-org/gitlab/-/issues/352526) removed. + +Similar to how you [share a project with a group](../project/members/share_project_with_groups.md), +you can share a group with another group. To invite a group, you must be a member of it. Members get direct access +to the shared group. This includes members who inherited group membership from a parent group. + +To share a given group, for example, `Frontend` with another group, for example, +`Engineering`: + +1. Go to the `Frontend` group. +1. On the left sidebar, select **Group information > Members**. +1. Select **Invite a group**. +1. In the **Select a group to invite** list, select `Engineering`. +1. Select a [role](../permissions.md) as maximum access level. +1. Select **Invite**. + +After sharing the `Frontend` group with the `Engineering` group: + +- The **Groups** tab lists the `Engineering` group. +- The **Groups** tab lists a group regardless of whether it is a public or private group. +- All members of the `Engineering` group have access to the `Frontend` group. The same access levels of the members apply up to the maximum access level selected when sharing the group. + +## Transfer a group + +You can transfer groups in the following ways: + +- Transfer a subgroup to a new parent group. +- Convert a top-level group into a subgroup by transferring it to the desired group. +- Convert a subgroup into a top-level group by transferring it out of its current group. + +When transferring groups, note: + +- Changing a group's parent can have unintended side effects. See [what happens when a repository path changes](../project/repository/index.md#what-happens-when-a-repository-path-changes). +- You can only transfer groups to groups you manage. +- You must update your local repositories to point to the new location. +- If the immediate parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects change to match the new parent group's visibility. +- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this leaves the group without an owner. In this case, the user transferring the group becomes the group's owner. +- Transfers fail if [packages](../packages/index.md) exist in any of the projects in the group, or in any of its subgroups. + +## Enable delayed project deletion **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/220382) in GitLab 13.2. +> - [Inheritance and enforcement added](https://gitlab.com/gitlab-org/gitlab/-/issues/321724) in GitLab 13.11. +> - [Instance setting to enable by default added](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2. +> - [Instance setting is inherited and enforced when disabled](https://gitlab.com/gitlab-org/gitlab/-/issues/352960) in GitLab 15.1. +> - [User interface changed](https://gitlab.com/gitlab-org/gitlab/-/issues/352961) in GitLab 15.1. + +[Delayed project deletion](../project/settings/index.md#delayed-project-deletion) is locked and disabled unless the instance-level settings for +[deletion protection](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) is enabled for either groups only or groups and projects. +When enabled on groups, projects in the group are deleted after a period of delay. During this period, projects are in a read-only state and can be restored. +The default period is seven days but [is configurable at the instance level](../admin_area/settings/visibility_and_access_controls.md#retention-period). + +On self-managed GitLab, projects are deleted immediately by default. +In GitLab 14.2 and later, an administrator can +[change the default setting](../admin_area/settings/visibility_and_access_controls.md#deletion-protection) +for projects in newly-created groups. + +On GitLab.com, see the [GitLab.com settings page](../gitlab_com/index.md#delayed-project-deletion) for +the default setting. + +To enable delayed deletion of projects in a group: + +1. Go to the group's **Settings > General** page. +1. Expand the **Permissions and group features** section. +1. Scroll to: + - (GitLab 15.1 and later) **Deletion protection** and select **Keep deleted projects**. + - (GitLab 15.0 and earlier) **Enable delayed project deletion** and tick the checkbox. +1. Optional. To prevent subgroups from changing this setting, select: + - (GitLab 15.1 and later), **Enforce deletion protection for all subgroups** + - (GitLab 15.0 and earlier), **Enforce for all subgroups**. +1. Select **Save changes**. + +NOTE: +In GitLab 13.11 and above the group setting for delayed project deletion is inherited by subgroups. As discussed in [Cascading settings](../../development/cascading_settings.md) inheritance can be overridden, unless enforced by an ancestor. + +## Disable email notifications + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/23585) in GitLab 12.2. + +You can disable all email notifications related to the group, which includes its subgroups and projects. + +To disable email notifications: + +1. Go to the group's **Settings > General** page. +1. Expand the **Permissions and group features** section. +1. Select **Disable email notifications**. +1. Select **Save changes**. + +## Disable group mentions + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/21301) in GitLab 12.6. + +You can prevent users from being added to a conversation and getting notified when +anyone [mentions a group](../discussions/index.md#mentions) +in which those users are members. + +Groups with disabled mentions are visualized accordingly in the autocompletion dropdown list. + +This is particularly helpful for groups with a large number of users. + +To disable group mentions: + +1. Go to the group's **Settings > General** page. +1. Expand the **Permissions and group features** section. +1. Select **Disable group mentions**. +1. Select **Save changes**. + +## User cap for groups + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330027) in GitLab 14.7. + +FLAG: +On self-managed GitLab, this feature is not available. On GitLab.com, this feature is available for some groups. +This feature is not ready for production use. + +When the number of billable members reaches the user cap, new users can't be added to the group +without being approved by the group owner. + +Groups with the user cap feature enabled have [group sharing](#share-a-group-with-another-group) +disabled for the group and its subgroups. + +### Specify a user cap for a group + +Prerequisite: + +- You must be assigned the Owner role) for the group. + +To specify a user cap: + +1. On the top bar, select **Menu > Groups** and find your group. + You can set a cap on the top-level group only. +1. On the left sidebar, select **Settings > General**. +1. Expand **Permissions and group features**. +1. In the **User cap** box, enter the desired number of users. +1. Select **Save changes**. + +If you already have more users in the group than the user cap value, users +are not removed. However, you can't add more without approval. + +Increasing the user cap does not approve pending members. + +### Remove the user cap for a group + +You can remove the user cap, so there is no limit on the number of members you can add to a group. + +Prerequisite: + +- You must be assigned the Owner role) for the group. + +To remove the user cap: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > General**. +1. Expand **Permissions and group features**. +1. In the **User cap** box, delete the value. +1. Select **Save changes**. + +Decreasing the user cap does not approve pending members. + +### Approve pending members for a group + +When the number of billable users reaches the user cap, any new member is put in a pending state +and must be approved. + +Pending members do not count as billable. Members count as billable only after they have been approved and are no longer in a pending state. + +Prerequisite: + +- You must be assigned the Owner role) for the group. + +To approve members that are pending because they've exceeded the user cap: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Settings > Usage Quotas**. +1. On the **Seats** tab, under the alert, select **View pending approvals**. +1. For each member you want to approve, select **Approve**. + +## Group file templates **(PREMIUM)** + +Use group file templates to share a set of templates for common file +types with every project in a group. It is analogous to the +[instance template repository](../admin_area/settings/instance_template_repository.md). +The selected project should follow the same naming conventions as +are documented on that page. + +You can only choose projects in the group as the template source. +This includes projects shared with the group, but it **excludes** projects in +subgroups or parent groups of the group being configured. + +You can configure this feature for both subgroups and immediate parent groups. A project +in a subgroup has access to the templates for that subgroup, as well as +any immediate parent groups. + +To learn how to create templates for issues and merge requests, see +[Description templates](../project/description_templates.md). + +Define project templates at a group level by setting a group as the template source. +[Learn more about group-level project templates](custom_project_templates.md). + +### Enable group file template **(PREMIUM)** + +To enable group file templates: + +1. Go to the group's **Settings > General** page. +1. Expand the **Templates** section. +1. Choose a project to act as the template repository. +1. Select **Save changes**. + +## Group merge request approval settings **(PREMIUM)** + +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/285458) in GitLab 13.9. [Deployed behind the `group_merge_request_approval_settings_feature_flag` flag](../../administration/feature_flags.md), disabled by default. +> - [Enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/285410) in GitLab 14.5. +> - [Feature flag `group_merge_request_approval_settings_feature_flag`](https://gitlab.com/gitlab-org/gitlab/-/issues/343872) removed in GitLab 14.9. + +Group approval settings manage [project merge request approval settings](../project/merge_requests/approvals/settings.md) +at the top-level group level. These settings [cascade to all projects](../project/merge_requests/approvals/settings.md#settings-cascading) +that belong to the group. + +To view the merge request approval settings for a group: + +1. Go to the top-level group's **Settings > General** page. +1. Expand the **Merge request approvals** section. +1. Select the settings you want. +1. Select **Save changes**. + +Support for group-level settings for merge request approval rules is tracked in this [epic](https://gitlab.com/groups/gitlab-org/-/epics/4367). + +## Group activity analytics **(PREMIUM)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207164) in GitLab 12.10 as a [Beta feature](../../policy/alpha-beta-support.md#beta-features). + +For a group, you can view how many merge requests, issues, and members were created in the last 90 days. + +These Group Activity Analytics can be enabled with the `group_activity_analytics` [feature flag](../../development/feature_flags/index.md#enabling-a-feature-flag-locally-in-development). + + + +Changes to [group wikis](../project/wiki/group.md) do not appear in group activity analytics. + +### View group activity + +You can view the most recent actions taken in a group, either in your browser or in an RSS feed: + +1. On the top bar, select **Menu > Groups**. +1. Select **Your Groups**. +1. Find the group and select it. +1. On the left sidebar, select **Group information > Activity**. + +To view the activity feed in Atom format, select the +**RSS** (**{rss}**) icon. + +## Troubleshooting + +### Validation errors on namespaces and groups + +[GitLab 14.4 and later](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70365) performs +the following checks when creating or updating namespaces or groups: + +- Namespaces must not have parents. +- Group parents must be groups and not namespaces. + +In the unlikely event that you see these errors in your GitLab installation, +[contact Support](https://about.gitlab.com/support/) so that we can improve this validation. diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md index bf4e13779fd..0d84606eb11 100644 --- a/doc/user/group/subgroups/index.md +++ b/doc/user/group/subgroups/index.md @@ -102,7 +102,7 @@ Subgroup members can: 1. Be [direct members](../../project/members/index.md#add-users-to-a-project) of the subgroup. 1. [Inherit membership](../../project/members/index.md#inherited-membership) of the subgroup from the subgroup's parent group. -1. Be a member of a group that was [shared with the subgroup's top-level group](../index.md#share-a-group-with-another-group). +1. Be a member of a group that was [shared with the subgroup's top-level group](../manage.md#share-a-group-with-another-group). ```mermaid flowchart RL @@ -161,7 +161,7 @@ In the screenshot above: - Administrator has the Owner role on group _Four_ and is a member of all subgroups. For that reason, as with User 3, the **Source** column indicates they are a direct member. -Members can be [filtered by inherited or direct membership](../index.md#filter-a-group). +Members can be [filtered by inherited or direct membership](../manage.md#filter-a-group). ### Override ancestor group membership diff --git a/doc/user/packages/container_registry/index.md b/doc/user/packages/container_registry/index.md index d0c771ecc41..606e2cb135d 100644 --- a/doc/user/packages/container_registry/index.md +++ b/doc/user/packages/container_registry/index.md @@ -563,7 +563,7 @@ project or branch name. Special characters can include: - Leading underscore - Trailing hyphen/dash -To get around this, you can [change the group path](../../group/index.md#change-a-groups-path), +To get around this, you can [change the group path](../../group/manage.md#change-a-groups-path), [change the project path](../../project/settings/index.md#rename-a-repository) or change the branch name. diff --git a/doc/user/permissions.md b/doc/user/permissions.md index b01bfbef3aa..33c1584cb72 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -439,9 +439,9 @@ The following table lists group permissions available for each role: 2. Introduced in GitLab 12.2. 3. Default project creation role can be changed at: - The [instance level](admin_area/settings/visibility_and_access_controls.md#define-which-roles-can-create-projects). - - The [group level](group/index.md#specify-who-can-add-projects-to-a-group). + - The [group level](group/manage.md#specify-who-can-add-projects-to-a-group). 4. Does not apply to subgroups. -5. Developers can push commits to the default branch of a new project only if the [default branch protection](group/index.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected". +5. Developers can push commits to the default branch of a new project only if the [default branch protection](group/manage.md#change-the-default-branch-protection-of-a-group) is set to "Partially protected" or "Not protected". 6. In addition, if your group is public or internal, all users who can see the group can also see group wiki pages. 7. Users can only view events based on their individual actions. diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md index 42287ff84ce..5df3a973cca 100644 --- a/doc/user/project/description_templates.md +++ b/doc/user/project/description_templates.md @@ -112,7 +112,7 @@ To re-use templates [you've created](../project/description_templates.md#create-  You might also be interested in templates for various -[file types in groups](../group/index.md#group-file-templates). +[file types in groups](../group/manage.md#group-file-templates). ### Set a default template for merge requests and issues diff --git a/doc/user/project/integrations/webhook_events.md b/doc/user/project/integrations/webhook_events.md index 32e5f949c15..871ada102e0 100644 --- a/doc/user/project/integrations/webhook_events.md +++ b/doc/user/project/integrations/webhook_events.md @@ -1614,7 +1614,7 @@ Payload example: ### Remove a subgroup from a group -This webhook is not triggered when a [subgroup is transferred to a new parent group](../../group/index.md#transfer-a-group). +This webhook is not triggered when a [subgroup is transferred to a new parent group](../../group/manage.md#transfer-a-group). Request header: diff --git a/doc/user/project/issues/design_management.md b/doc/user/project/issues/design_management.md index d1b27f6eab0..bf3cd13f3f0 100644 --- a/doc/user/project/issues/design_management.md +++ b/doc/user/project/issues/design_management.md @@ -276,7 +276,7 @@ It's rendered as: User activity events on designs (creation, deletion, and updates) are tracked by GitLab and displayed on the [user profile](../../profile/index.md#access-your-user-profile), -[group](../../group/index.md#view-group-activity), +[group](../../group/manage.md#view-group-activity), and [project](../working_with_projects.md#view-project-activity) activity pages. ## GitLab-Figma plugin diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md index ff5f2ac8cb6..2f12a963754 100644 --- a/doc/user/project/members/index.md +++ b/doc/user/project/members/index.md @@ -15,7 +15,7 @@ Project members can: 1. Be [direct members](#add-users-to-a-project) of the project. 1. [Inherit membership](#inherited-membership) of the project from the project's group. 1. Be a member of a group that was [shared](share_project_with_groups.md) with the project. -1. Be a member of a group that was [shared with the project's group](../../group/index.md#share-a-group-with-another-group). +1. Be a member of a group that was [shared with the project's group](../../group/manage.md#share-a-group-with-another-group). ```mermaid flowchart RL diff --git a/doc/user/project/merge_requests/approvals/index.md b/doc/user/project/merge_requests/approvals/index.md index 014936208c6..9f33ed6807b 100644 --- a/doc/user/project/merge_requests/approvals/index.md +++ b/doc/user/project/merge_requests/approvals/index.md @@ -23,7 +23,7 @@ flexibility: and require their approval before work can merge. You can configure merge request approvals on a per-project basis, and -[on the group level](../../../group/index.md#group-merge-request-approval-settings). Administrators of +[on the group level](../../../group/manage.md#group-merge-request-approval-settings). Administrators of [GitLab Premium](https://about.gitlab.com/pricing/) and [GitLab Ultimate](https://about.gitlab.com/pricing/) self-managed GitLab instances can also configure approvals diff --git a/doc/user/project/merge_requests/approvals/rules.md b/doc/user/project/merge_requests/approvals/rules.md index b79c8ee867f..70112135ba9 100644 --- a/doc/user/project/merge_requests/approvals/rules.md +++ b/doc/user/project/merge_requests/approvals/rules.md @@ -172,15 +172,15 @@ oversight on proposed work. To enable approval permissions for these users witho granting them push access: 1. [Create a protected branch](../../protected_branches.md) -1. [Create a new group](../../../group/index.md#create-a-group). -1. [Add the user to the group](../../../group/index.md#add-users-to-a-group), +1. [Create a new group](../../../group/manage.md#create-a-group). +1. [Add the user to the group](../../../group/manage.md#add-users-to-a-group), and select the Reporter role for the user. 1. [Share the project with your group](../../members/share_project_with_groups.md#share-a-project-with-a-group-of-users), based on the Reporter role. 1. Go to your project and select **Settings > General**. 1. Expand **Merge request (MR) approvals**. 1. Select **Add approval rule** or **Update approval rule** and target the protected branch. -1. [Add the group](../../../group/index.md#create-a-group) to the permission list. +1. [Add the group](../../../group/manage.md#create-a-group) to the permission list.  diff --git a/doc/user/project/merge_requests/approvals/settings.md b/doc/user/project/merge_requests/approvals/settings.md index 7b865a91106..c07582c2ae6 100644 --- a/doc/user/project/merge_requests/approvals/settings.md +++ b/doc/user/project/merge_requests/approvals/settings.md @@ -139,7 +139,7 @@ You can also enforce merge request approval settings: - At the [instance level](../../../admin_area/merge_requests_approvals.md), which apply to all groups on an instance and, therefore, all projects. -- On a [top-level group](../../../group/index.md#group-merge-request-approval-settings), which apply to all subgroups +- On a [top-level group](../../../group/manage.md#group-merge-request-approval-settings), which apply to all subgroups and projects. If the settings are inherited by a group or project, they cannot be changed in the group or project diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md index 747da817195..3083ca5da3c 100644 --- a/doc/user/project/repository/branches/default.md +++ b/doc/user/project/repository/branches/default.md @@ -110,7 +110,7 @@ This setting applies only to each repository's default branch. To protect other you must either: - Configure [branch protection in the repository](../../../project/protected_branches.md). -- Configure [branch protection for groups](../../../group/index.md#change-the-default-branch-protection-of-a-group). +- Configure [branch protection for groups](../../../group/manage.md#change-the-default-branch-protection-of-a-group). Administrators of self-managed instances can customize the initial default branch protection for projects hosted on that instance. Individual groups and subgroups can override this instance-wide setting for their projects. diff --git a/doc/user/project/repository/index.md b/doc/user/project/repository/index.md index a8937d4f705..8e1286548b9 100644 --- a/doc/user/project/repository/index.md +++ b/doc/user/project/repository/index.md @@ -232,7 +232,7 @@ When a repository path changes, GitLab handles the transition from the old location to the new one with a redirect. When you [rename a user](../../profile/index.md#change-your-username), -[change a group path](../../group/index.md#change-a-groups-path), or [rename a repository](../settings/index.md#rename-a-repository): +[change a group path](../../group/manage.md#change-a-groups-path), or [rename a repository](../settings/index.md#rename-a-repository): - URLs for the namespace and everything under it, like projects, are redirected to the new URLs. diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 7d1bfcaab59..bd6b107fbfd 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -392,7 +392,7 @@ When you transfer a project to another namespace, you move the project to a diff Prerequisites: -- You must have at least the Maintainer role for the [group](../../group/index.md#create-a-group) to which you are transferring. +- You must have at least the Maintainer role for the [group](../../group/manage.md#create-a-group) to which you are transferring. - You must be the Owner of the project you transfer. - The group must allow creation of new projects. - The project must not contain any [container images](../../packages/container_registry/index.md#limitations). @@ -455,7 +455,7 @@ delayed project deletion is enabled for a particular project: - Self-managed instance [settings](../../admin_area/settings/visibility_and_access_controls.md#delayed-project-deletion). You can enable delayed project deletion as the default setting for new groups, and configure the number of days for the delay. For GitLab.com, see the [GitLab.com settings](../../gitlab_com/index.md#delayed-project-deletion). -- Group [settings](../../group/index.md#enable-delayed-project-deletion) to enabled delayed project deletion for all +- Group [settings](../../group/manage.md#enable-delayed-project-deletion) to enabled delayed project deletion for all projects in the group. ### Delete a project immediately diff --git a/doc/user/project/wiki/group.md b/doc/user/project/wiki/group.md index dc448fed970..a3ba5789d39 100644 --- a/doc/user/project/wiki/group.md +++ b/doc/user/project/wiki/group.md @@ -15,7 +15,7 @@ Group wikis are similar to [project wikis](index.md), with a few limitations: - [Git LFS](../../../topics/git/lfs/index.md) is not supported. - Group wikis are not included in [global search](../../search/advanced_search.md). -- Changes to group wikis don't show up in the [group's activity feed](../../group/index.md#group-activity-analytics). +- Changes to group wikis don't show up in the [group's activity feed](../../group/manage.md#group-activity-analytics). - Group wikis are enabled by default for GitLab Premium and higher tiers. You [can't turn them off from the GitLab user interface](https://gitlab.com/gitlab-org/gitlab/-/issues/208413). diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md index 6e320923496..c1f7436f716 100644 --- a/doc/user/project/wiki/index.md +++ b/doc/user/project/wiki/index.md @@ -230,7 +230,7 @@ GitLab tracks wiki creation, deletion, and update events. These events are displ - [User profile](../../profile/index.md#access-your-user-profile). - Activity pages, depending on the type of wiki: - - [Group activity](../../group/index.md#view-group-activity). + - [Group activity](../../group/manage.md#view-group-activity). - [Project activity](../working_with_projects.md#view-project-activity). Commits to wikis are not counted in [repository analytics](../../analytics/repository_analytics.md). diff --git a/doc/user/project/working_with_projects.md b/doc/user/project/working_with_projects.md index 9572bc241fc..6d8c077b537 100644 --- a/doc/user/project/working_with_projects.md +++ b/doc/user/project/working_with_projects.md @@ -290,7 +290,7 @@ To view your personal projects: ## Delete a project After you delete a project, projects in personal namespaces are deleted immediately. To delay deletion of projects in a group -you can [enable delayed project removal](../group/index.md#enable-delayed-project-deletion). +you can [enable delayed project removal](../group/manage.md#enable-delayed-project-deletion). To delete a project: @@ -310,7 +310,7 @@ To delete a project: > - [Available to all users](https://gitlab.com/gitlab-org/gitlab/-/issues/346976) in GitLab 14.8 [with a flag](../../administration/feature_flags.md) named `project_owners_list_project_pending_deletion`. Enabled by default. > - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/351556) in GitLab 14.9. [Feature flag `project_owners_list_project_pending_deletion`](https://gitlab.com/gitlab-org/gitlab/-/issues/351556) removed. -When delayed project deletion is [enabled for a group](../group/index.md#enable-delayed-project-deletion), +When delayed project deletion is [enabled for a group](../group/manage.md#enable-delayed-project-deletion), projects within that group are not deleted immediately, but only after a delay. To view a list of all projects that are pending deletion: diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1259f261f8d..d07818c19f5 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -33708,6 +33708,9 @@ msgstr "" msgid "Runners|Online:" msgstr "" +msgid "Runners|Outdated" +msgstr "" + msgid "Runners|Pause from accepting jobs" msgstr "" @@ -33955,6 +33958,9 @@ msgstr "" msgid "Runners|active" msgstr "" +msgid "Runners|available" +msgstr "" + msgid "Runners|group" msgstr "" @@ -33970,6 +33976,9 @@ msgstr "" msgid "Runners|paused" msgstr "" +msgid "Runners|recommended" +msgstr "" + msgid "Runners|shared" msgstr "" @@ -34114,6 +34123,21 @@ msgstr "" msgid "Saving project." msgstr "" +msgid "ScanExecutionPolicy|%{ifLabelStart}if%{ifLabelEnd} %{rules} for the %{branches} branch(es)" +msgstr "" + +msgid "ScanExecutionPolicy|A pipeline is run" +msgstr "" + +msgid "ScanExecutionPolicy|Schedule" +msgstr "" + +msgid "ScanExecutionPolicy|Schedule rule component" +msgstr "" + +msgid "ScanExecutionPolicy|Select branches" +msgstr "" + msgid "ScanResultPolicy|%{ifLabelStart}if%{ifLabelEnd} %{scanners} find(s) more than %{vulnerabilitiesAllowed} %{severities} %{vulnerabilityStates} vulnerabilities in an open merge request targeting %{branches}" msgstr "" @@ -46139,6 +46163,9 @@ msgstr "" msgid "i18n|%{language} (%{percent_translated}%% translated)" msgstr "" +msgid "if" +msgstr "" + msgid "image diff" msgstr "" diff --git a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js index aa1aa723491..fac99ed395a 100644 --- a/spec/frontend/runner/admin_runners/admin_runners_app_spec.js +++ b/spec/frontend/runner/admin_runners/admin_runners_app_spec.js @@ -1,4 +1,4 @@ -import Vue from 'vue'; +import Vue, { nextTick } from 'vue'; import { GlToast, GlLink } from '@gitlab/ui'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; @@ -22,7 +22,6 @@ import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_ import RunnerList from '~/runner/components/runner_list.vue'; import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vue'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; -import RunnerCount from '~/runner/components/stat/runner_count.vue'; import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue'; import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue'; import RunnerPagination from '~/runner/components/runner_pagination.vue'; @@ -42,7 +41,7 @@ import { RUNNER_PAGE_SIZE, } from '~/runner/constants'; import allRunnersQuery from 'ee_else_ce/runner/graphql/list/all_runners.query.graphql'; -import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql'; +import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql'; import { captureException } from '~/runner/sentry_utils'; import { @@ -266,22 +265,17 @@ describe('AdminRunnersApp', () => { describe('when a filter is preselected', () => { beforeEach(async () => { - setWindowLocation(`?status[]=${STATUS_ONLINE}&runner_type[]=${INSTANCE_TYPE}&tag[]=tag1`); + setWindowLocation(`?status[]=${STATUS_ONLINE}&runner_type[]=${INSTANCE_TYPE}&paused[]=true`); - await createComponent({ - stubs: { - RunnerStats, - RunnerCount, - }, - }); + await createComponent({ mountFn: mountExtended }); }); it('sets the filters in the search bar', () => { expect(findRunnerFilteredSearchBar().props('value')).toEqual({ runnerType: INSTANCE_TYPE, filters: [ - { type: 'status', value: { data: STATUS_ONLINE, operator: '=' } }, - { type: 'tag', value: { data: 'tag1', operator: '=' } }, + { type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }, + { type: PARAM_KEY_PAUSED, value: { data: 'true', operator: '=' } }, ], sort: 'CREATED_DESC', pagination: { page: 1 }, @@ -292,7 +286,7 @@ describe('AdminRunnersApp', () => { expect(mockRunnersHandler).toHaveBeenLastCalledWith({ status: STATUS_ONLINE, type: INSTANCE_TYPE, - tagList: ['tag1'], + paused: true, sort: DEFAULT_SORT, first: RUNNER_PAGE_SIZE, }); @@ -302,41 +296,34 @@ describe('AdminRunnersApp', () => { expect(mockRunnersCountHandler).toHaveBeenCalledWith({ type: INSTANCE_TYPE, status: STATUS_ONLINE, - tagList: ['tag1'], + paused: true, }); }); }); describe('when a filter is selected by the user', () => { - beforeEach(() => { - createComponent({ - stubs: { - RunnerStats, - RunnerCount, - }, - }); + beforeEach(async () => { + await createComponent({ mountFn: mountExtended }); findRunnerFilteredSearchBar().vm.$emit('input', { runnerType: null, - filters: [ - { type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }, - { type: PARAM_KEY_TAG, value: { data: 'tag1', operator: '=' } }, - ], + filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }], sort: CREATED_ASC, }); + + await nextTick(); }); it('updates the browser url', () => { expect(updateHistory).toHaveBeenLastCalledWith({ title: expect.any(String), - url: expect.stringContaining('?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC'), + url: expect.stringContaining('?status[]=ONLINE&sort=CREATED_ASC'), }); }); it('requests the runners with filters', () => { expect(mockRunnersHandler).toHaveBeenLastCalledWith({ status: STATUS_ONLINE, - tagList: ['tag1'], sort: CREATED_ASC, first: RUNNER_PAGE_SIZE, }); @@ -344,7 +331,6 @@ describe('AdminRunnersApp', () => { it('fetches count results for requested status', () => { expect(mockRunnersCountHandler).toHaveBeenCalledWith({ - tagList: ['tag1'], status: STATUS_ONLINE, }); }); diff --git a/spec/frontend/runner/components/stat/runner_count_spec.js b/spec/frontend/runner/components/stat/runner_count_spec.js index 89b51b1b4a7..2a6a745099f 100644 --- a/spec/frontend/runner/components/stat/runner_count_spec.js +++ b/spec/frontend/runner/components/stat/runner_count_spec.js @@ -7,8 +7,8 @@ import createMockApollo from 'helpers/mock_apollo_helper'; import waitForPromises from 'helpers/wait_for_promises'; import { captureException } from '~/runner/sentry_utils'; -import allRunnersCountQuery from '~/runner/graphql/list/all_runners_count.query.graphql'; -import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql'; +import allRunnersCountQuery from 'ee_else_ce/runner/graphql/list/all_runners_count.query.graphql'; +import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql'; import { runnersCountData, groupRunnersCountData } from '../../mock_data'; diff --git a/spec/frontend/runner/components/stat/runner_single_stat_spec.js b/spec/frontend/runner/components/stat/runner_single_stat_spec.js new file mode 100644 index 00000000000..964a6a6ff71 --- /dev/null +++ b/spec/frontend/runner/components/stat/runner_single_stat_spec.js @@ -0,0 +1,61 @@ +import { GlSingleStat } from '@gitlab/ui/dist/charts'; +import { shallowMount } from '@vue/test-utils'; +import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue'; +import RunnerCount from '~/runner/components/stat/runner_count.vue'; +import { INSTANCE_TYPE, GROUP_TYPE } from '~/runner/constants'; + +describe('RunnerStats', () => { + let wrapper; + + const findRunnerCount = () => wrapper.findComponent(RunnerCount); + const findGlSingleStat = () => wrapper.findComponent(GlSingleStat); + + const createComponent = ({ props = {}, count, mountFn = shallowMount, ...options } = {}) => { + wrapper = mountFn(RunnerSingleStat, { + propsData: { + scope: INSTANCE_TYPE, + title: 'My title', + variables: {}, + ...props, + }, + stubs: { + RunnerCount: { + props: ['scope', 'variables', 'skip'], + render() { + return this.$scopedSlots.default({ + count, + }); + }, + }, + }, + ...options, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + it.each` + case | count | value + ${'number'} | ${99} | ${'99'} + ${'long number'} | ${1000} | ${'1,000'} + ${'empty number'} | ${null} | ${'-'} + `('formats $case', ({ count, value }) => { + createComponent({ count }); + + expect(findGlSingleStat().props('value')).toBe(value); + }); + + it('Passes runner count props', () => { + const props = { + scope: GROUP_TYPE, + variables: { paused: true }, + skip: true, + }; + + createComponent({ props }); + + expect(findRunnerCount().props()).toEqual(props); + }); +}); diff --git a/spec/frontend/runner/components/stat/runner_stats_spec.js b/spec/frontend/runner/components/stat/runner_stats_spec.js index f1ba6403dfb..241c0c886df 100644 --- a/spec/frontend/runner/components/stat/runner_stats_spec.js +++ b/spec/frontend/runner/components/stat/runner_stats_spec.js @@ -1,15 +1,13 @@ import { shallowMount, mount } from '@vue/test-utils'; import { s__ } from '~/locale'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; -import RunnerCount from '~/runner/components/stat/runner_count.vue'; import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue'; import { INSTANCE_TYPE, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants'; describe('RunnerStats', () => { let wrapper; - const findRunnerCountAt = (i) => wrapper.findAllComponents(RunnerCount).at(i); - const findRunnerStatusStatAt = (i) => wrapper.findAllComponents(RunnerStatusStat).at(i); + const findStatusStats = () => wrapper.findAllComponents(RunnerStatusStat).wrappers; const createComponent = ({ props = {}, mountFn = shallowMount, ...options } = {}) => { wrapper = mountFn(RunnerStats, { @@ -53,31 +51,12 @@ describe('RunnerStats', () => { expect(text).toMatch(`${s__('Runners|Stale runners')} 1`); }); - it('Displays counts for filtered searches', () => { - createComponent({ props: { variables: { paused: true } } }); + it('Displays all counts for filtered searches', () => { + const mockVariables = { paused: true }; + createComponent({ props: { variables: mockVariables } }); - expect(findRunnerCountAt(0).props('variables').paused).toBe(true); - expect(findRunnerCountAt(1).props('variables').paused).toBe(true); - expect(findRunnerCountAt(2).props('variables').paused).toBe(true); - }); - - it('Skips overlapping statuses', () => { - createComponent({ props: { variables: { status: STATUS_ONLINE } } }); - - expect(findRunnerCountAt(0).props('skip')).toBe(false); - expect(findRunnerCountAt(1).props('skip')).toBe(true); - expect(findRunnerCountAt(2).props('skip')).toBe(true); - }); - - it.each` - i | status - ${0} | ${STATUS_ONLINE} - ${1} | ${STATUS_OFFLINE} - ${2} | ${STATUS_STALE} - `('Displays status $status at index $i', ({ i, status }) => { - createComponent({ mountFn: mount }); - - expect(findRunnerCountAt(i).props('variables').status).toBe(status); - expect(findRunnerStatusStatAt(i).props('status')).toBe(status); + findStatusStats().forEach((stat) => { + expect(stat.props('variables')).toEqual(mockVariables); + }); }); }); diff --git a/spec/frontend/runner/components/stat/runner_status_stat_spec.js b/spec/frontend/runner/components/stat/runner_status_stat_spec.js index 3218272eac7..428b476866e 100644 --- a/spec/frontend/runner/components/stat/runner_status_stat_spec.js +++ b/spec/frontend/runner/components/stat/runner_status_stat_spec.js @@ -1,18 +1,18 @@ -import { GlSingleStat } from '@gitlab/ui/dist/charts'; -import { shallowMount, mount } from '@vue/test-utils'; +import { shallowMount } from '@vue/test-utils'; import RunnerStatusStat from '~/runner/components/stat/runner_status_stat.vue'; -import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE } from '~/runner/constants'; +import RunnerSingleStat from '~/runner/components/stat/runner_single_stat.vue'; +import { STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE, INSTANCE_TYPE } from '~/runner/constants'; describe('RunnerStatusStat', () => { let wrapper; - const findSingleStat = () => wrapper.findComponent(GlSingleStat); + const findRunnerSingleStat = () => wrapper.findComponent(RunnerSingleStat); - const createComponent = ({ props = {} } = {}, mountFn = shallowMount) => { - wrapper = mountFn(RunnerStatusStat, { + const createComponent = ({ props = {} } = {}) => { + wrapper = shallowMount(RunnerStatusStat, { propsData: { + scope: INSTANCE_TYPE, status: STATUS_ONLINE, - value: 99, ...props, }, }); @@ -23,45 +23,44 @@ describe('RunnerStatusStat', () => { }); describe.each` - status | variant | title | badge + status | variant | title | metaText ${STATUS_ONLINE} | ${'success'} | ${'Online runners'} | ${'online'} ${STATUS_OFFLINE} | ${'muted'} | ${'Offline runners'} | ${'offline'} ${STATUS_STALE} | ${'warning'} | ${'Stale runners'} | ${'stale'} - `('Renders a stat for status "$status"', ({ status, variant, title, badge }) => { + `('Renders a stat for status "$status"', ({ status, variant, title, metaText }) => { beforeEach(() => { - createComponent({ props: { status } }, mount); + createComponent({ props: { status } }); }); it('Renders text', () => { - expect(wrapper.text()).toMatch(new RegExp(`${title} 99\\s+${badge}`)); + expect(findRunnerSingleStat().attributes()).toMatchObject({ + variant, + title, + metatext: metaText, + }); }); - it(`Uses variant ${variant}`, () => { - expect(findSingleStat().props('variant')).toBe(variant); + it('Passes filters', () => { + expect(findRunnerSingleStat().props('variables')).toEqual({ status }); }); - }); - - it('Formats stat number', () => { - createComponent({ props: { value: 1000 } }, mount); - - expect(wrapper.text()).toMatch('Online runners 1,000'); - }); - it('Shows a null result', () => { - createComponent({ props: { value: null } }, mount); - - expect(wrapper.text()).toMatch('Online runners -'); + it('Does not skip query with no filters', () => { + expect(findRunnerSingleStat().props('skip')).toEqual(false); + }); }); - it('Shows an undefined result', () => { - createComponent({ props: { value: undefined } }, mount); + it('Merges filters', () => { + createComponent({ props: { status: STATUS_ONLINE, variables: { paused: true } } }); - expect(wrapper.text()).toMatch('Online runners -'); + expect(findRunnerSingleStat().props('variables')).toEqual({ + status: STATUS_ONLINE, + paused: true, + }); }); - it('Shows result for an unknown status', () => { - createComponent({ props: { status: 'UNKNOWN' } }, mount); + it('Skips query when other status is in the filters', () => { + createComponent({ props: { status: STATUS_ONLINE, variables: { status: STATUS_OFFLINE } } }); - expect(wrapper.text()).toMatch('Runners 99'); + expect(findRunnerSingleStat().props('skip')).toEqual(true); }); }); diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js index 9c42b0d6865..2aa631f270f 100644 --- a/spec/frontend/runner/group_runners/group_runners_app_spec.js +++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js @@ -19,7 +19,6 @@ import RunnerFilteredSearchBar from '~/runner/components/runner_filtered_search_ import RunnerList from '~/runner/components/runner_list.vue'; import RunnerListEmptyState from '~/runner/components/runner_list_empty_state.vue'; import RunnerStats from '~/runner/components/stat/runner_stats.vue'; -import RunnerCount from '~/runner/components/stat/runner_count.vue'; import RunnerActionsCell from '~/runner/components/cells/runner_actions_cell.vue'; import RegistrationDropdown from '~/runner/components/registration/registration_dropdown.vue'; import RunnerPagination from '~/runner/components/runner_pagination.vue'; @@ -32,7 +31,6 @@ import { GROUP_TYPE, PARAM_KEY_PAUSED, PARAM_KEY_STATUS, - PARAM_KEY_TAG, STATUS_ONLINE, STATUS_OFFLINE, STATUS_STALE, @@ -40,7 +38,7 @@ import { I18N_EDIT, } from '~/runner/constants'; import groupRunnersQuery from '~/runner/graphql/list/group_runners.query.graphql'; -import groupRunnersCountQuery from '~/runner/graphql/list/group_runners_count.query.graphql'; +import groupRunnersCountQuery from 'ee_else_ce/runner/graphql/list/group_runners_count.query.graphql'; import GroupRunnersApp from '~/runner/group_runners/group_runners_app.vue'; import { captureException } from '~/runner/sentry_utils'; import { @@ -111,7 +109,7 @@ describe('GroupRunnersApp', () => { return waitForPromises(); }; - beforeEach(async () => { + beforeEach(() => { mockGroupRunnersHandler.mockResolvedValue(groupRunnersData); mockGroupRunnersCountHandler.mockResolvedValue(groupRunnersCountData); }); @@ -254,12 +252,7 @@ describe('GroupRunnersApp', () => { beforeEach(async () => { setWindowLocation(`?status[]=${STATUS_ONLINE}&runner_type[]=${INSTANCE_TYPE}`); - await createComponent({ - stubs: { - RunnerStats, - RunnerCount, - }, - }); + await createComponent({ mountFn: mountExtended }); }); it('sets the filters in the search bar', () => { @@ -292,19 +285,11 @@ describe('GroupRunnersApp', () => { describe('when a filter is selected by the user', () => { beforeEach(async () => { - createComponent({ - stubs: { - RunnerStats, - RunnerCount, - }, - }); + await createComponent({ mountFn: mountExtended }); findRunnerFilteredSearchBar().vm.$emit('input', { runnerType: null, - filters: [ - { type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }, - { type: PARAM_KEY_TAG, value: { data: 'tag1', operator: '=' } }, - ], + filters: [{ type: PARAM_KEY_STATUS, value: { data: STATUS_ONLINE, operator: '=' } }], sort: CREATED_ASC, }); @@ -314,7 +299,7 @@ describe('GroupRunnersApp', () => { it('updates the browser url', () => { expect(updateHistory).toHaveBeenLastCalledWith({ title: expect.any(String), - url: expect.stringContaining('?status[]=ONLINE&tag[]=tag1&sort=CREATED_ASC'), + url: expect.stringContaining('?status[]=ONLINE&sort=CREATED_ASC'), }); }); @@ -322,7 +307,6 @@ describe('GroupRunnersApp', () => { expect(mockGroupRunnersHandler).toHaveBeenLastCalledWith({ groupFullPath: mockGroupFullPath, status: STATUS_ONLINE, - tagList: ['tag1'], sort: CREATED_ASC, first: RUNNER_PAGE_SIZE, }); @@ -331,7 +315,6 @@ describe('GroupRunnersApp', () => { it('fetches count results for requested status', () => { expect(mockGroupRunnersCountHandler).toHaveBeenCalledWith({ groupFullPath: mockGroupFullPath, - tagList: ['tag1'], status: STATUS_ONLINE, }); }); diff --git a/spec/frontend/vue_shared/alert_details/alert_details_spec.js b/spec/frontend/vue_shared/alert_details/alert_details_spec.js index ce51af31a70..59e21b2ff40 100644 --- a/spec/frontend/vue_shared/alert_details/alert_details_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_details_spec.js @@ -285,14 +285,14 @@ describe('AlertDetails', () => { }); it('displays a loading state when loading', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); }); describe('error state', () => { it('displays a error state correctly', () => { mountComponent({ data: { errored: true } }); - expect(wrapper.find(GlAlert).exists()).toBe(true); + expect(wrapper.findComponent(GlAlert).exists()).toBe(true); }); it('renders html-errors correctly', () => { @@ -304,7 +304,7 @@ describe('AlertDetails', () => { it('does not display an error when dismissed', () => { mountComponent({ data: { errored: true, isErrorDismissed: true } }); - expect(wrapper.find(GlAlert).exists()).toBe(false); + expect(wrapper.findComponent(GlAlert).exists()).toBe(false); }); }); diff --git a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js index 1216681038f..cf04c1eb24a 100644 --- a/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_metrics_spec.js @@ -28,8 +28,8 @@ describe('Alert Metrics', () => { }); } - const findChart = () => wrapper.find(MetricEmbed); - const findEmptyState = () => wrapper.find({ ref: 'emptyState' }); + const findChart = () => wrapper.findComponent(MetricEmbed); + const findEmptyState = () => wrapper.findComponent({ ref: 'emptyState' }); afterEach(() => { if (wrapper) { diff --git a/spec/frontend/vue_shared/alert_details/alert_status_spec.js b/spec/frontend/vue_shared/alert_details/alert_status_spec.js index ba3b0335a8e..2a37ff2b784 100644 --- a/spec/frontend/vue_shared/alert_details/alert_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/alert_status_spec.js @@ -13,7 +13,7 @@ describe('AlertManagementStatus', () => { let wrapper; const findStatusDropdown = () => wrapper.findComponent(GlDropdown); const findFirstStatusOption = () => findStatusDropdown().findComponent(GlDropdownItem); - const findAllStatusOptions = () => findStatusDropdown().findAll(GlDropdownItem); + const findAllStatusOptions = () => findStatusDropdown().findAllComponents(GlDropdownItem); const findStatusDropdownHeader = () => wrapper.findByTestId('dropdown-header'); const selectFirstStatusOption = () => { diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js index 29569734621..5a0ee5a59ba 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_assignees_spec.js @@ -128,7 +128,7 @@ describe('Alert Details Sidebar Assignees', () => { wrapper.setData({ isDropdownSearching: false }); await nextTick(); - wrapper.find(SidebarAssignee).vm.$emit('update-alert-assignees', 'root'); + wrapper.findComponent(SidebarAssignee).vm.$emit('update-alert-assignees', 'root'); expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ mutation: AlertSetAssignees, @@ -156,7 +156,7 @@ describe('Alert Details Sidebar Assignees', () => { jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(errorMutationResult); await nextTick(); - const SideBarAssigneeItem = wrapper.findAll(SidebarAssignee).at(0); + const SideBarAssigneeItem = wrapper.findAllComponents(SidebarAssignee).at(0); await SideBarAssigneeItem.vm.$emit('update-alert-assignees'); expect(wrapper.emitted('alert-error')).toBeDefined(); }); diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js index ef75e038bff..3b38349622f 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_spec.js @@ -65,7 +65,7 @@ describe('Alert Details Sidebar', () => { mountMethod: mount, alert: mockAlert, }); - expect(wrapper.find(SidebarAssignees).exists()).toBe(true); + expect(wrapper.findComponent(SidebarAssignees).exists()).toBe(true); }); it('should render side bar status dropdown', () => { @@ -73,7 +73,7 @@ describe('Alert Details Sidebar', () => { mountMethod: mount, alert: mockAlert, }); - expect(wrapper.find(SidebarStatus).exists()).toBe(true); + expect(wrapper.findComponent(SidebarStatus).exists()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js index a5a9fb55737..6a750bb99c0 100644 --- a/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js +++ b/spec/frontend/vue_shared/alert_details/system_notes/alert_management_system_note_spec.js @@ -31,7 +31,7 @@ describe('Alert Details System Note', () => { it('renders the correct system note', () => { const noteId = wrapper.find('.note-wrapper').attributes('id'); - const iconName = wrapper.find(GlIcon).attributes('name'); + const iconName = wrapper.findComponent(GlIcon).attributes('name'); expect(noteId).toBe('note_1628'); expect(iconName).toBe(mockAlert.notes.nodes[0].systemNoteIconName); diff --git a/spec/frontend/vue_shared/components/actions_button_spec.js b/spec/frontend/vue_shared/components/actions_button_spec.js index e5b7b693cb5..07c53c04723 100644 --- a/spec/frontend/vue_shared/components/actions_button_spec.js +++ b/spec/frontend/vue_shared/components/actions_button_spec.js @@ -45,9 +45,9 @@ describe('Actions button component', () => { return directiveBinding.value; }; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); const findButtonTooltip = () => getTooltip(findButton()); - const findDropdown = () => wrapper.find(GlDropdown); + const findDropdown = () => wrapper.findComponent(GlDropdown); const findDropdownTooltip = () => getTooltip(findDropdown()); const parseDropdownItems = () => findDropdown() diff --git a/spec/frontend/vue_shared/components/alert_details_table_spec.js b/spec/frontend/vue_shared/components/alert_details_table_spec.js index b9a8a5bee97..1304e811318 100644 --- a/spec/frontend/vue_shared/components/alert_details_table_spec.js +++ b/spec/frontend/vue_shared/components/alert_details_table_spec.js @@ -74,7 +74,7 @@ describe('AlertDetails', () => { }); it('displays a loading state when loading', () => { - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); }); diff --git a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js index d14f3e5559f..ce7fd40937f 100644 --- a/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js +++ b/spec/frontend/vue_shared/components/blob_viewers/rich_viewer_spec.js @@ -43,6 +43,6 @@ describe('Blob Rich Viewer component', () => { }); it('is using Markdown View Field', () => { - expect(wrapper.find(MarkdownFieldView).exists()).toBe(true); + expect(wrapper.findComponent(MarkdownFieldView).exists()).toBe(true); }); }); diff --git a/spec/frontend/vue_shared/components/changed_file_icon_spec.js b/spec/frontend/vue_shared/components/changed_file_icon_spec.js index 6b9658a6d18..229fa245284 100644 --- a/spec/frontend/vue_shared/components/changed_file_icon_spec.js +++ b/spec/frontend/vue_shared/components/changed_file_icon_spec.js @@ -25,7 +25,7 @@ describe('Changed file icon', () => { wrapper.destroy(); }); - const findIcon = () => wrapper.find(GlIcon); + const findIcon = () => wrapper.findComponent(GlIcon); const findIconName = () => findIcon().props('name'); const findIconClasses = () => findIcon().classes(); const findTooltipText = () => wrapper.attributes('title'); diff --git a/spec/frontend/vue_shared/components/ci_icon_spec.js b/spec/frontend/vue_shared/components/ci_icon_spec.js index 1b502f9587c..2064bee9673 100644 --- a/spec/frontend/vue_shared/components/ci_icon_spec.js +++ b/spec/frontend/vue_shared/components/ci_icon_spec.js @@ -22,7 +22,7 @@ describe('CI Icon component', () => { }); expect(wrapper.find('span').exists()).toBe(true); - expect(wrapper.find(GlIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).exists()).toBe(true); }); describe('active icons', () => { diff --git a/spec/frontend/vue_shared/components/clipboard_button_spec.js b/spec/frontend/vue_shared/components/clipboard_button_spec.js index fca5e664a96..b18b00e70bb 100644 --- a/spec/frontend/vue_shared/components/clipboard_button_spec.js +++ b/spec/frontend/vue_shared/components/clipboard_button_spec.js @@ -21,7 +21,7 @@ describe('clipboard button', () => { }); }; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); const expectConfirmationTooltip = async ({ event, message }) => { const title = 'Copy this value'; diff --git a/spec/frontend/vue_shared/components/clone_dropdown_spec.js b/spec/frontend/vue_shared/components/clone_dropdown_spec.js index eefd1838988..31c08260dd0 100644 --- a/spec/frontend/vue_shared/components/clone_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/clone_dropdown_spec.js @@ -38,9 +38,9 @@ describe('Clone Dropdown Button', () => { ${'HTTP'} | ${1} | ${httpLink} `('renders correct link and a copy-button for $name', ({ index, value }) => { createComponent(); - const group = wrapper.findAll(GlFormInputGroup).at(index); + const group = wrapper.findAllComponents(GlFormInputGroup).at(index); expect(group.props('value')).toBe(value); - expect(group.find(GlFormInputGroup).exists()).toBe(true); + expect(group.findComponent(GlFormInputGroup).exists()).toBe(true); }); it.each` @@ -50,8 +50,8 @@ describe('Clone Dropdown Button', () => { `('does not fail if only $name is set', ({ name, value }) => { createComponent({ [name]: value }); - expect(wrapper.find(GlFormInputGroup).props('value')).toBe(value); - expect(wrapper.findAll(GlDropdownSectionHeader).length).toBe(1); + expect(wrapper.findComponent(GlFormInputGroup).props('value')).toBe(value); + expect(wrapper.findAllComponents(GlDropdownSectionHeader).length).toBe(1); }); }); @@ -63,12 +63,12 @@ describe('Clone Dropdown Button', () => { `('allows null values for the props', ({ name, value }) => { createComponent({ ...defaultPropsData, [name]: value }); - expect(wrapper.findAll(GlDropdownSectionHeader).length).toBe(1); + expect(wrapper.findAllComponents(GlDropdownSectionHeader).length).toBe(1); }); it('correctly calculates httpLabel for HTTPS protocol', () => { createComponent({ httpLink: httpsLink }); - expect(wrapper.find(GlDropdownSectionHeader).text()).toContain('HTTPS'); + expect(wrapper.findComponent(GlDropdownSectionHeader).text()).toContain('HTTPS'); }); }); }); diff --git a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js index 8cbe0630426..060048c4bbd 100644 --- a/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js +++ b/spec/frontend/vue_shared/components/color_picker/color_picker_spec.js @@ -16,14 +16,14 @@ describe('ColorPicker', () => { const setColor = '#000000'; const invalidText = 'Please enter a valid hex (#RRGGBB or #RGB) color value'; - const findGlFormGroup = () => wrapper.find(GlFormGroup); + const findGlFormGroup = () => wrapper.findComponent(GlFormGroup); const colorPreview = () => wrapper.find('[data-testid="color-preview"]'); - const colorPicker = () => wrapper.find(GlFormInput); + const colorPicker = () => wrapper.findComponent(GlFormInput); const colorInput = () => wrapper.find('input[type="color"]'); - const colorTextInput = () => wrapper.find(GlFormInputGroup).find('input[type="text"]'); + const colorTextInput = () => wrapper.findComponent(GlFormInputGroup).find('input[type="text"]'); const invalidFeedback = () => wrapper.find('.invalid-feedback'); - const description = () => wrapper.find(GlFormGroup).attributes('description'); - const presetColors = () => wrapper.findAll(GlLink); + const description = () => wrapper.findComponent(GlFormGroup).attributes('description'); + const presetColors = () => wrapper.findAllComponents(GlLink); beforeEach(() => { gon.suggested_label_colors = { diff --git a/spec/frontend/vue_shared/components/commit_spec.js b/spec/frontend/vue_shared/components/commit_spec.js index d91853e7b79..1893e127f6f 100644 --- a/spec/frontend/vue_shared/components/commit_spec.js +++ b/spec/frontend/vue_shared/components/commit_spec.js @@ -9,11 +9,11 @@ describe('Commit component', () => { let wrapper; const findIcon = (name) => { - const icons = wrapper.findAll(GlIcon).filter((c) => c.attributes('name') === name); + const icons = wrapper.findAllComponents(GlIcon).filter((c) => c.attributes('name') === name); return icons.length ? icons.at(0) : icons; }; - const findUserAvatar = () => wrapper.find(UserAvatarLink); + const findUserAvatar = () => wrapper.findComponent(UserAvatarLink); const findRefName = () => wrapper.findByTestId('ref-name'); const createComponent = (propsData) => { @@ -47,7 +47,7 @@ describe('Commit component', () => { }, }); - expect(wrapper.find('.icon-container').find(GlIcon).exists()).toBe(true); + expect(wrapper.find('.icon-container').findComponent(GlIcon).exists()).toBe(true); }); describe('Given all the props', () => { diff --git a/spec/frontend/vue_shared/components/confirm_modal_spec.js b/spec/frontend/vue_shared/components/confirm_modal_spec.js index 3ca1c943398..0af789c78df 100644 --- a/spec/frontend/vue_shared/components/confirm_modal_spec.js +++ b/spec/frontend/vue_shared/components/confirm_modal_spec.js @@ -51,13 +51,13 @@ describe('vue_shared/components/confirm_modal', () => { wrapper.destroy(); }); - const findModal = () => wrapper.find(GlModalStub); + const findModal = () => wrapper.findComponent(GlModalStub); const findForm = () => wrapper.find('form'); const findFormData = () => findForm() .findAll('input') .wrappers.map((x) => ({ name: x.attributes('name'), value: x.attributes('value') })); - const findDomElementListener = () => wrapper.find(DomElementListener); + const findDomElementListener = () => wrapper.findComponent(DomElementListener); const triggerOpenWithEventHub = (modalData) => { eventHub.$emit(EVENT_OPEN_CONFIRM_MODAL, modalData); }; diff --git a/spec/frontend/vue_shared/components/dismissible_alert_spec.js b/spec/frontend/vue_shared/components/dismissible_alert_spec.js index 879d4aba441..5eb6e3166c6 100644 --- a/spec/frontend/vue_shared/components/dismissible_alert_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_alert_spec.js @@ -20,7 +20,7 @@ describe('vue_shared/components/dismissible_alert', () => { wrapper.destroy(); }); - const findAlert = () => wrapper.find(GlAlert); + const findAlert = () => wrapper.findComponent(GlAlert); describe('default', () => { beforeEach(() => { diff --git a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js index 084d0559665..dd3e55c82bb 100644 --- a/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js +++ b/spec/frontend/vue_shared/components/dropdown/dropdown_widget_spec.js @@ -8,7 +8,7 @@ describe('DropdownWidget component', () => { let wrapper; const findDropdown = () => wrapper.findComponent(GlDropdown); - const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); const findSearch = () => wrapper.findComponent(GlSearchBoxByType); const createComponent = ({ props = {} } = {}) => { diff --git a/spec/frontend/vue_shared/components/expand_button_spec.js b/spec/frontend/vue_shared/components/expand_button_spec.js index 87d6ed6b21f..170c947e520 100644 --- a/spec/frontend/vue_shared/components/expand_button_spec.js +++ b/spec/frontend/vue_shared/components/expand_button_spec.js @@ -37,11 +37,11 @@ describe('Expand button', () => { }); it('renders no text when short text is not provided', () => { - expect(wrapper.find(ExpandButton).text()).toBe(''); + expect(wrapper.findComponent(ExpandButton).text()).toBe(''); }); it('does not render expanded text', () => { - expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.short); + expect(wrapper.findComponent(ExpandButton).text().trim()).not.toBe(text.short); }); describe('when short text is provided', () => { @@ -55,13 +55,13 @@ describe('Expand button', () => { }); it('renders short text', () => { - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short); + expect(wrapper.findComponent(ExpandButton).text().trim()).toBe(text.short); }); it('renders button before text', () => { expect(expanderPrependEl().isVisible()).toBe(true); expect(expanderAppendEl().isVisible()).toBe(false); - expect(wrapper.find(ExpandButton).element).toMatchSnapshot(); + expect(wrapper.findComponent(ExpandButton).element).toMatchSnapshot(); }); }); @@ -81,7 +81,7 @@ describe('Expand button', () => { }); it('renders the expanded text', () => { - expect(wrapper.find(ExpandButton).text()).toContain(text.expanded); + expect(wrapper.findComponent(ExpandButton).text()).toContain(text.expanded); }); describe('when short text is provided', () => { @@ -98,13 +98,13 @@ describe('Expand button', () => { }); it('only renders expanded text', () => { - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded); + expect(wrapper.findComponent(ExpandButton).text().trim()).toBe(text.expanded); }); it('renders button after text', () => { expect(expanderPrependEl().isVisible()).toBe(false); expect(expanderAppendEl().isVisible()).toBe(true); - expect(wrapper.find(ExpandButton).element).toMatchSnapshot(); + expect(wrapper.findComponent(ExpandButton).element).toMatchSnapshot(); }); }); }); @@ -124,11 +124,11 @@ describe('Expand button', () => { }); it('clicking hides expanded text', async () => { - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded); + expect(wrapper.findComponent(ExpandButton).text().trim()).toBe(text.expanded); expanderAppendEl().trigger('click'); await nextTick(); - expect(wrapper.find(ExpandButton).text().trim()).not.toBe(text.expanded); + expect(wrapper.findComponent(ExpandButton).text().trim()).not.toBe(text.expanded); }); describe('when short text is provided', () => { @@ -145,11 +145,11 @@ describe('Expand button', () => { }); it('clicking reveals short text', async () => { - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.expanded); + expect(wrapper.findComponent(ExpandButton).text().trim()).toBe(text.expanded); expanderAppendEl().trigger('click'); await nextTick(); - expect(wrapper.find(ExpandButton).text().trim()).toBe(text.short); + expect(wrapper.findComponent(ExpandButton).text().trim()).toBe(text.short); }); }); }); diff --git a/spec/frontend/vue_shared/components/file_icon_spec.js b/spec/frontend/vue_shared/components/file_icon_spec.js index b0e623520a8..3f4bfc86b67 100644 --- a/spec/frontend/vue_shared/components/file_icon_spec.js +++ b/spec/frontend/vue_shared/components/file_icon_spec.js @@ -6,7 +6,7 @@ import { FILE_SYMLINK_MODE } from '~/vue_shared/constants'; describe('File Icon component', () => { let wrapper; const findSvgIcon = () => wrapper.find('svg'); - const findGlIcon = () => wrapper.find(GlIcon); + const findGlIcon = () => wrapper.findComponent(GlIcon); const getIconName = () => findSvgIcon() .find('use') @@ -61,7 +61,7 @@ describe('File Icon component', () => { loading: true, }); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); }); it('should add a special class and a size class', () => { diff --git a/spec/frontend/vue_shared/components/file_row_spec.js b/spec/frontend/vue_shared/components/file_row_spec.js index 62fb29c455c..f5a545891d5 100644 --- a/spec/frontend/vue_shared/components/file_row_spec.js +++ b/spec/frontend/vue_shared/components/file_row_spec.js @@ -119,7 +119,7 @@ describe('File row component', () => { level: 0, }); - expect(wrapper.find(FileHeader).exists()).toBe(true); + expect(wrapper.findComponent(FileHeader).exists()).toBe(true); }); it('matches the current route against encoded file URL', () => { @@ -164,6 +164,6 @@ describe('File row component', () => { level: 0, }); - expect(wrapper.find(FileIcon).props('submodule')).toBe(submodule); + expect(wrapper.findComponent(FileIcon).props('submodule')).toBe(submodule); }); }); diff --git a/spec/frontend/vue_shared/components/file_tree_spec.js b/spec/frontend/vue_shared/components/file_tree_spec.js index 39a7c7a2b3a..e8818e09dc0 100644 --- a/spec/frontend/vue_shared/components/file_tree_spec.js +++ b/spec/frontend/vue_shared/components/file_tree_spec.js @@ -25,8 +25,8 @@ describe('File Tree component', () => { }); }; - const findFileRow = () => wrapper.find(MockFileRow); - const findChildrenTrees = () => wrapper.findAll(FileTree).wrappers.slice(1); + const findFileRow = () => wrapper.findComponent(MockFileRow); + const findChildrenTrees = () => wrapper.findAllComponents(FileTree).wrappers.slice(1); const findChildrenTreeProps = () => findChildrenTrees().map((x) => ({ ...x.props(), diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js index e44bc8771f5..1b9ca8e6092 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/filtered_search_bar_root_spec.js @@ -88,10 +88,10 @@ describe('FilteredSearchBarRoot', () => { expect(wrapper.vm.filterValue).toEqual([]); expect(wrapper.vm.selectedSortOption).toBe(mockSortOptions[0]); expect(wrapper.vm.selectedSortDirection).toBe(SortDirection.descending); - expect(wrapper.find(GlButtonGroup).exists()).toBe(true); - expect(wrapper.find(GlButton).exists()).toBe(true); - expect(wrapper.find(GlDropdown).exists()).toBe(true); - expect(wrapper.find(GlDropdownItem).exists()).toBe(true); + expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true); + expect(wrapper.findComponent(GlButton).exists()).toBe(true); + expect(wrapper.findComponent(GlDropdown).exists()).toBe(true); + expect(wrapper.findComponent(GlDropdownItem).exists()).toBe(true); }); it('does not initialize `selectedSortOption` and `selectedSortDirection` when `sortOptions` is not applied and hides the sort dropdown', () => { @@ -99,10 +99,10 @@ describe('FilteredSearchBarRoot', () => { expect(wrapperNoSort.vm.filterValue).toEqual([]); expect(wrapperNoSort.vm.selectedSortOption).toBe(undefined); - expect(wrapperNoSort.find(GlButtonGroup).exists()).toBe(false); - expect(wrapperNoSort.find(GlButton).exists()).toBe(false); - expect(wrapperNoSort.find(GlDropdown).exists()).toBe(false); - expect(wrapperNoSort.find(GlDropdownItem).exists()).toBe(false); + expect(wrapperNoSort.findComponent(GlButtonGroup).exists()).toBe(false); + expect(wrapperNoSort.findComponent(GlButton).exists()).toBe(false); + expect(wrapperNoSort.findComponent(GlDropdown).exists()).toBe(false); + expect(wrapperNoSort.findComponent(GlDropdownItem).exists()).toBe(false); }); }); @@ -217,7 +217,7 @@ describe('FilteredSearchBarRoot', () => { it('emits component event `onFilter` with empty array and true when initially selected filter value was cleared', async () => { wrapper = createComponent({ initialFilterValue: [tokenValueLabel] }); - wrapper.find(GlFilteredSearch).vm.$emit('clear'); + wrapper.findComponent(GlFilteredSearch).vm.$emit('clear'); await nextTick(); expect(wrapper.emitted('onFilter')[0]).toEqual([[], true]); @@ -362,7 +362,7 @@ describe('FilteredSearchBarRoot', () => { it('calls `blurSearchInput` method to remove focus from filter input field', () => { jest.spyOn(wrapper.vm, 'blurSearchInput'); - wrapper.find(GlFilteredSearch).vm.$emit('submit', mockFilters); + wrapper.findComponent(GlFilteredSearch).vm.$emit('submit', mockFilters); expect(wrapper.vm.blurSearchInput).toHaveBeenCalled(); }); @@ -392,7 +392,7 @@ describe('FilteredSearchBarRoot', () => { }); it('renders gl-filtered-search component', () => { - const glFilteredSearchEl = wrapper.find(GlFilteredSearch); + const glFilteredSearchEl = wrapper.findComponent(GlFilteredSearch); expect(glFilteredSearchEl.props('placeholder')).toBe('Filter requirements'); expect(glFilteredSearchEl.props('availableTokens')).toEqual(mockAvailableTokens); @@ -404,8 +404,10 @@ describe('FilteredSearchBarRoot', () => { showCheckbox: true, }); - expect(wrapperWithCheckbox.find(GlFormCheckbox).exists()).toBe(true); - expect(wrapperWithCheckbox.find(GlFormCheckbox).attributes('checked')).not.toBeDefined(); + expect(wrapperWithCheckbox.findComponent(GlFormCheckbox).exists()).toBe(true); + expect( + wrapperWithCheckbox.findComponent(GlFormCheckbox).attributes('checked'), + ).not.toBeDefined(); wrapperWithCheckbox.destroy(); @@ -414,7 +416,7 @@ describe('FilteredSearchBarRoot', () => { checkboxChecked: true, }); - expect(wrapperWithCheckbox.find(GlFormCheckbox).attributes('checked')).toBe('true'); + expect(wrapperWithCheckbox.findComponent(GlFormCheckbox).attributes('checked')).toBe('true'); wrapperWithCheckbox.destroy(); }); @@ -448,7 +450,7 @@ describe('FilteredSearchBarRoot', () => { await nextTick(); - expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := Direct'); + expect(wrapperFullMount.findComponent(GlDropdownItem).text()).toBe('Membership := Direct'); wrapperFullMount.destroy(); }); @@ -466,20 +468,20 @@ describe('FilteredSearchBarRoot', () => { await nextTick(); - expect(wrapperFullMount.find(GlDropdownItem).text()).toBe('Membership := exclude'); + expect(wrapperFullMount.findComponent(GlDropdownItem).text()).toBe('Membership := exclude'); wrapperFullMount.destroy(); }); }); it('renders sort dropdown component', () => { - expect(wrapper.find(GlButtonGroup).exists()).toBe(true); - expect(wrapper.find(GlDropdown).exists()).toBe(true); - expect(wrapper.find(GlDropdown).props('text')).toBe(mockSortOptions[0].title); + expect(wrapper.findComponent(GlButtonGroup).exists()).toBe(true); + expect(wrapper.findComponent(GlDropdown).exists()).toBe(true); + expect(wrapper.findComponent(GlDropdown).props('text')).toBe(mockSortOptions[0].title); }); it('renders sort dropdown items', () => { - const dropdownItemsEl = wrapper.findAll(GlDropdownItem); + const dropdownItemsEl = wrapper.findAllComponents(GlDropdownItem); expect(dropdownItemsEl).toHaveLength(mockSortOptions.length); expect(dropdownItemsEl.at(0).text()).toBe(mockSortOptions[0].title); @@ -488,7 +490,7 @@ describe('FilteredSearchBarRoot', () => { }); it('renders sort direction button', () => { - const sortButtonEl = wrapper.find(GlButton); + const sortButtonEl = wrapper.findComponent(GlButton); expect(sortButtonEl.attributes('title')).toBe('Sort direction: Descending'); expect(sortButtonEl.props('icon')).toBe('sort-highest'); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js index 3f24d5df858..302dfabffb2 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/author_token_spec.js @@ -195,7 +195,7 @@ describe('AuthorToken', () => { }); await nextTick(); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // Author, =, "Administrator" @@ -207,7 +207,7 @@ describe('AuthorToken', () => { it('renders token value with correct avatarUrl from author object', async () => { const getAvatarEl = () => - wrapper.findAll(GlFilteredSearchTokenSegment).at(2).findComponent(GlAvatar); + wrapper.findAllComponents(GlFilteredSearchTokenSegment).at(2).findComponent(GlAvatar); wrapper = createComponent({ value: { data: mockAuthors[0].username }, @@ -252,7 +252,7 @@ describe('AuthorToken', () => { await activateSuggestionsList(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultAuthors.length + currentUserLength); defaultAuthors.forEach((label, index) => { @@ -266,12 +266,12 @@ describe('AuthorToken', () => { config: { ...mockAuthorToken, defaultAuthors: [] }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_NONE_ANY` as default suggestions', async () => { @@ -283,7 +283,7 @@ describe('AuthorToken', () => { await activateSuggestionsList(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(2 + currentUserLength); expect(suggestions.at(0).text()).toBe(DEFAULT_NONE_ANY[0].text); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js index 7b495ec9bee..1de35daa3a5 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/branch_token_spec.js @@ -114,7 +114,7 @@ describe('BranchToken', () => { describe('template', () => { const defaultBranches = DEFAULT_NONE_ANY; async function showSuggestions() { - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); @@ -133,11 +133,11 @@ describe('BranchToken', () => { }); it('renders gl-filtered-search-token component', () => { - expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true); + expect(wrapper.findComponent(GlFilteredSearchToken).exists()).toBe(true); }); it('renders token item when value is selected', () => { - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); expect(tokenSegments.at(2).text()).toBe(mockBranches[0].name); @@ -150,7 +150,7 @@ describe('BranchToken', () => { stubs: { Portal: true }, }); await showSuggestions(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultBranches.length); defaultBranches.forEach((branch, index) => { @@ -166,8 +166,8 @@ describe('BranchToken', () => { }); await showSuggestions(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders no suggestions as default', async () => { @@ -177,7 +177,7 @@ describe('BranchToken', () => { stubs: { Portal: true }, }); await showSuggestions(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(0); }); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js index 157e021fc60..c9879987931 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_contact_token_spec.js @@ -195,7 +195,7 @@ describe('CrmContactToken', () => { value: { data: '1' }, }); - const baseTokenEl = wrapper.find(BaseToken); + const baseTokenEl = wrapper.findComponent(BaseToken); expect(baseTokenEl.exists()).toBe(true); expect(baseTokenEl.props()).toMatchObject({ @@ -210,7 +210,7 @@ describe('CrmContactToken', () => { value: { data: `${getIdFromGraphQLId(contact.id)}` }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // Contact, =, Contact name expect(tokenSegments.at(2).text()).toBe(`${contact.firstName} ${contact.lastName}`); // Contact name @@ -222,12 +222,12 @@ describe('CrmContactToken', () => { config: { ...mockCrmContactToken, defaultContacts }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultContacts.length); defaultContacts.forEach((contact, index) => { @@ -241,13 +241,13 @@ describe('CrmContactToken', () => { config: { ...mockCrmContactToken, defaultContacts: [] }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_NONE_ANY` as default suggestions', () => { @@ -256,11 +256,11 @@ describe('CrmContactToken', () => { config: { ...mockCrmContactToken }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(DEFAULT_NONE_ANY.length); DEFAULT_NONE_ANY.forEach((contact, index) => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js index 977f8bbef61..16333b052e6 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/crm_organization_token_spec.js @@ -194,7 +194,7 @@ describe('CrmOrganizationToken', () => { value: { data: '1' }, }); - const baseTokenEl = wrapper.find(BaseToken); + const baseTokenEl = wrapper.findComponent(BaseToken); expect(baseTokenEl.exists()).toBe(true); expect(baseTokenEl.props()).toMatchObject({ @@ -209,7 +209,7 @@ describe('CrmOrganizationToken', () => { value: { data: `${getIdFromGraphQLId(organization.id)}` }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // Organization, =, Organization name expect(tokenSegments.at(2).text()).toBe(organization.name); // Organization name @@ -221,12 +221,12 @@ describe('CrmOrganizationToken', () => { config: { ...mockCrmOrganizationToken, defaultOrganizations }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultOrganizations.length); defaultOrganizations.forEach((organization, index) => { @@ -240,13 +240,13 @@ describe('CrmOrganizationToken', () => { config: { ...mockCrmOrganizationToken, defaultOrganizations: [] }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_NONE_ANY` as default suggestions', () => { @@ -255,11 +255,11 @@ describe('CrmOrganizationToken', () => { config: { ...mockCrmOrganizationToken }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(DEFAULT_NONE_ANY.length); DEFAULT_NONE_ANY.forEach((organization, index) => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js index dcb0d095b1b..bf4a6eb7635 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/emoji_token_spec.js @@ -135,14 +135,16 @@ describe('EmojiToken', () => { }); it('renders gl-filtered-search-token component', () => { - expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true); + expect(wrapper.findComponent(GlFilteredSearchToken).exists()).toBe(true); }); it('renders token item when value is selected', () => { - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // My Reaction, =, "thumbsup" - expect(tokenSegments.at(2).find(GlEmoji).attributes('data-name')).toEqual('thumbsup'); + expect(tokenSegments.at(2).findComponent(GlEmoji).attributes('data-name')).toEqual( + 'thumbsup', + ); }); it('renders provided defaultEmojis as suggestions', async () => { @@ -151,12 +153,12 @@ describe('EmojiToken', () => { config: { ...mockReactionEmojiToken, defaultEmojis }, stubs: { Portal: true, GlEmoji }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultEmojis.length); defaultEmojis.forEach((emoji, index) => { @@ -170,13 +172,13 @@ describe('EmojiToken', () => { config: { ...mockReactionEmojiToken, defaultEmojis: [] }, stubs: { Portal: true, GlEmoji }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_LABEL_NONE` and `DEFAULT_LABEL_ANY` as default suggestions', async () => { @@ -185,12 +187,12 @@ describe('EmojiToken', () => { config: { ...mockReactionEmojiToken }, stubs: { Portal: true, GlEmoji }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(2); expect(suggestions.at(0).text()).toBe(DEFAULT_LABEL_NONE.text); diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js index 51161a1a0ef..01e281884ed 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/label_token_spec.js @@ -156,7 +156,7 @@ describe('LabelToken', () => { }); it('renders base-token component', () => { - const baseTokenEl = wrapper.find(BaseToken); + const baseTokenEl = wrapper.findComponent(BaseToken); expect(baseTokenEl.exists()).toBe(true); expect(baseTokenEl.props()).toMatchObject({ @@ -166,7 +166,7 @@ describe('LabelToken', () => { }); it('renders token item when value is selected', () => { - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // Label, =, "Foo Label" expect(tokenSegments.at(2).text()).toBe(`~${mockRegularLabel.title}`); // "Foo Label" @@ -181,12 +181,12 @@ describe('LabelToken', () => { config: { ...mockLabelToken, defaultLabels }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultLabels.length); defaultLabels.forEach((label, index) => { @@ -200,13 +200,13 @@ describe('LabelToken', () => { config: { ...mockLabelToken, defaultLabels: [] }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_NONE_ANY` as default suggestions', () => { @@ -215,11 +215,11 @@ describe('LabelToken', () => { config: { ...mockLabelToken }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(DEFAULT_NONE_ANY.length); DEFAULT_NONE_ANY.forEach((label, index) => { diff --git a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js index 7c545f76c0b..f71ba51fc5b 100644 --- a/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js +++ b/spec/frontend/vue_shared/components/filtered_search_bar/tokens/milestone_token_spec.js @@ -155,11 +155,11 @@ describe('MilestoneToken', () => { }); it('renders gl-filtered-search-token component', () => { - expect(wrapper.find(GlFilteredSearchToken).exists()).toBe(true); + expect(wrapper.findComponent(GlFilteredSearchToken).exists()).toBe(true); }); it('renders token item when value is selected', () => { - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); expect(tokenSegments).toHaveLength(3); // Milestone, =, '%"4.0"' expect(tokenSegments.at(2).text()).toBe(`%${mockRegularMilestone.title}`); // "4.0 RC1" @@ -171,12 +171,12 @@ describe('MilestoneToken', () => { config: { ...mockMilestoneToken, defaultMilestones }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(defaultMilestones.length); defaultMilestones.forEach((milestone, index) => { @@ -190,13 +190,13 @@ describe('MilestoneToken', () => { config: { ...mockMilestoneToken, defaultMilestones: [] }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - expect(wrapper.find(GlFilteredSearchSuggestion).exists()).toBe(false); - expect(wrapper.find(GlDropdownDivider).exists()).toBe(false); + expect(wrapper.findComponent(GlFilteredSearchSuggestion).exists()).toBe(false); + expect(wrapper.findComponent(GlDropdownDivider).exists()).toBe(false); }); it('renders `DEFAULT_MILESTONES` as default suggestions', async () => { @@ -205,12 +205,12 @@ describe('MilestoneToken', () => { config: { ...mockMilestoneToken }, stubs: { Portal: true }, }); - const tokenSegments = wrapper.findAll(GlFilteredSearchTokenSegment); + const tokenSegments = wrapper.findAllComponents(GlFilteredSearchTokenSegment); const suggestionsSegment = tokenSegments.at(2); suggestionsSegment.vm.$emit('activate'); await nextTick(); - const suggestions = wrapper.findAll(GlFilteredSearchSuggestion); + const suggestions = wrapper.findAllComponents(GlFilteredSearchSuggestion); expect(suggestions).toHaveLength(DEFAULT_MILESTONES.length); DEFAULT_MILESTONES.forEach((milestone, index) => { diff --git a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js index c0a6588833e..2dcd91f737f 100644 --- a/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js +++ b/spec/frontend/vue_shared/components/gl_modal_vuex_spec.js @@ -59,7 +59,7 @@ describe('GlModalVuex', () => { default: `<div>${TEST_SLOT}</div>`, }, }); - const glModal = wrapper.find(GlModal); + const glModal = wrapper.findComponent(GlModal); expect(glModal.props('modalId')).toBe(TEST_MODAL_ID); expect(glModal.text()).toContain(TEST_SLOT); @@ -76,7 +76,7 @@ describe('GlModalVuex', () => { okVariant, }, }); - const glModal = wrapper.find(GlModal); + const glModal = wrapper.findComponent(GlModal); expect(glModal.attributes('title')).toEqual(title); expect(glModal.attributes('oktitle')).toEqual(title); @@ -90,7 +90,7 @@ describe('GlModalVuex', () => { listeners: { ok }, }); - const glModal = wrapper.find(GlModal); + const glModal = wrapper.findComponent(GlModal); glModal.vm.$emit('ok'); expect(ok).toHaveBeenCalledTimes(1); @@ -101,7 +101,7 @@ describe('GlModalVuex', () => { factory(); - const glModal = wrapper.find(GlModal); + const glModal = wrapper.findComponent(GlModal); glModal.vm.$emit('shown'); expect(actions.show).toHaveBeenCalledTimes(1); @@ -112,7 +112,7 @@ describe('GlModalVuex', () => { factory(); - const glModal = wrapper.find(GlModal); + const glModal = wrapper.findComponent(GlModal); glModal.vm.$emit('hidden'); expect(actions.hide).toHaveBeenCalledTimes(1); diff --git a/spec/frontend/vue_shared/components/help_popover_spec.js b/spec/frontend/vue_shared/components/help_popover_spec.js index 64dce194327..6fd5ae0e946 100644 --- a/spec/frontend/vue_shared/components/help_popover_spec.js +++ b/spec/frontend/vue_shared/components/help_popover_spec.js @@ -7,8 +7,8 @@ describe('HelpPopover', () => { const title = 'popover <strong>title</strong>'; const content = 'popover <b>content</b>'; - const findQuestionButton = () => wrapper.find(GlButton); - const findPopover = () => wrapper.find(GlPopover); + const findQuestionButton = () => wrapper.findComponent(GlButton); + const findPopover = () => wrapper.findComponent(GlPopover); const createComponent = ({ props, ...opts } = {}) => { wrapper = mount(HelpPopover, { diff --git a/spec/frontend/vue_shared/components/integration_help_text_spec.js b/spec/frontend/vue_shared/components/integration_help_text_spec.js index c0e8b719007..c63e46313b3 100644 --- a/spec/frontend/vue_shared/components/integration_help_text_spec.js +++ b/spec/frontend/vue_shared/components/integration_help_text_spec.js @@ -30,9 +30,9 @@ describe('IntegrationHelpText component', () => { it('should use the gl components', () => { wrapper = createComponent(); - expect(wrapper.find(GlSprintf).exists()).toBe(true); - expect(wrapper.find(GlIcon).exists()).toBe(true); - expect(wrapper.find(GlLink).exists()).toBe(true); + expect(wrapper.findComponent(GlSprintf).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlLink).exists()).toBe(true); }); it('should render the help text', () => { @@ -44,9 +44,9 @@ describe('IntegrationHelpText component', () => { it('should not use the gl-link and gl-icon components', () => { wrapper = createComponent({ message: 'Click nowhere!' }); - expect(wrapper.find(GlSprintf).exists()).toBe(true); - expect(wrapper.find(GlIcon).exists()).toBe(false); - expect(wrapper.find(GlLink).exists()).toBe(false); + expect(wrapper.findComponent(GlSprintf).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlLink).exists()).toBe(false); }); it('should not render the link when start and end is not provided', () => { diff --git a/spec/frontend/vue_shared/components/markdown/header_spec.js b/spec/frontend/vue_shared/components/markdown/header_spec.js index 8a9af812c89..eccf5df82d3 100644 --- a/spec/frontend/vue_shared/components/markdown/header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/header_spec.js @@ -21,7 +21,7 @@ describe('Markdown field header component', () => { const findWriteTab = () => wrapper.findByTestId('write-tab'); const findPreviewTab = () => wrapper.findByTestId('preview-tab'); const findToolbar = () => wrapper.findByTestId('md-header-toolbar'); - const findToolbarButtons = () => wrapper.findAll(ToolbarButton); + const findToolbarButtons = () => wrapper.findAllComponents(ToolbarButton); const findToolbarButtonByProp = (prop, value) => findToolbarButtons() .filter((button) => button.props(prop) === value) diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js index 9944267cf24..9db1b779a04 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_header_spec.js @@ -38,13 +38,13 @@ describe('Suggestion Diff component', () => { wrapper.destroy(); }); - const findApplyButton = () => wrapper.find(ApplySuggestion); + const findApplyButton = () => wrapper.findComponent(ApplySuggestion); const findApplyBatchButton = () => wrapper.find('.js-apply-batch-btn'); const findAddToBatchButton = () => wrapper.find('.js-add-to-batch-btn'); const findRemoveFromBatchButton = () => wrapper.find('.js-remove-from-batch-btn'); const findHeader = () => wrapper.find('.js-suggestion-diff-header'); const findHelpButton = () => wrapper.find('.js-help-btn'); - const findLoading = () => wrapper.find(GlLoadingIcon); + const findLoading = () => wrapper.findComponent(GlLoadingIcon); it('renders a suggestion header', () => { createComponent(); diff --git a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js index af27e953776..d84483c1663 100644 --- a/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js +++ b/spec/frontend/vue_shared/components/markdown/suggestion_diff_spec.js @@ -71,7 +71,7 @@ describe('Suggestion Diff component', () => { }); it('renders a correct amount of suggestion diff rows', () => { - expect(wrapper.findAll(SuggestionDiffRow)).toHaveLength(3); + expect(wrapper.findAllComponents(SuggestionDiffRow)).toHaveLength(3); }); it.each` @@ -81,14 +81,14 @@ describe('Suggestion Diff component', () => { ${'addToBatch'} | ${[]} | ${[suggestionId]} ${'removeFromBatch'} | ${[]} | ${[suggestionId]} `('emits $event event on sugestion diff header $event', ({ event, childArgs, args }) => { - wrapper.find(SuggestionDiffHeader).vm.$emit(event, ...childArgs); + wrapper.findComponent(SuggestionDiffHeader).vm.$emit(event, ...childArgs); expect(wrapper.emitted(event)).toBeDefined(); expect(wrapper.emitted(event)).toEqual([args]); }); it('passes suggestion batch props to suggestion diff header', () => { - expect(wrapper.find(SuggestionDiffHeader).props()).toMatchObject({ + expect(wrapper.findComponent(SuggestionDiffHeader).props()).toMatchObject({ batchSuggestionsCount: 1, isBatched: true, isApplyingBatch: MOCK_DATA.suggestion.is_applying_batch, diff --git a/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js index 19e4f2d8c92..82210e79799 100644 --- a/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js +++ b/spec/frontend/vue_shared/components/markdown/toolbar_button_spec.js @@ -26,7 +26,7 @@ describe('toolbar_button', () => { }); const getButtonShortcutsAttr = () => { - return wrapper.find(GlButton).attributes('data-md-shortcuts'); + return wrapper.findComponent(GlButton).attributes('data-md-shortcuts'); }; describe('keyboard shortcuts', () => { diff --git a/spec/frontend/vue_shared/components/memory_graph_spec.js b/spec/frontend/vue_shared/components/memory_graph_spec.js index 53b96bd1b98..ae8d5ff78ba 100644 --- a/spec/frontend/vue_shared/components/memory_graph_spec.js +++ b/spec/frontend/vue_shared/components/memory_graph_spec.js @@ -47,7 +47,7 @@ describe('MemoryGraph', () => { it('should draw container with chart', () => { expect(wrapper.element).toMatchSnapshot(); expect(wrapper.find('.memory-graph-container').exists()).toBe(true); - expect(wrapper.find(GlSparklineChart).exists()).toBe(true); + expect(wrapper.findComponent(GlSparklineChart).exists()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/navigation_tabs_spec.js b/spec/frontend/vue_shared/components/navigation_tabs_spec.js index 30a89fed12f..b1bec28bffb 100644 --- a/spec/frontend/vue_shared/components/navigation_tabs_spec.js +++ b/spec/frontend/vue_shared/components/navigation_tabs_spec.js @@ -44,7 +44,7 @@ describe('navigation tabs component', () => { }); it('should render tabs', () => { - expect(wrapper.findAll(GlTab)).toHaveLength(data.length); + expect(wrapper.findAllComponents(GlTab)).toHaveLength(data.length); }); it('should render active tab', () => { diff --git a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js index 99b65ca6937..17a62ae8a33 100644 --- a/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js +++ b/spec/frontend/vue_shared/components/notes/noteable_warning_spec.js @@ -6,10 +6,11 @@ import NoteableWarning from '~/vue_shared/components/notes/noteable_warning.vue' describe('Issue Warning Component', () => { let wrapper; - const findIcon = (w = wrapper) => w.find(GlIcon); - const findLockedBlock = (w = wrapper) => w.find({ ref: 'locked' }); - const findConfidentialBlock = (w = wrapper) => w.find({ ref: 'confidential' }); - const findLockedAndConfidentialBlock = (w = wrapper) => w.find({ ref: 'lockedAndConfidential' }); + const findIcon = (w = wrapper) => w.findComponent(GlIcon); + const findLockedBlock = (w = wrapper) => w.findComponent({ ref: 'locked' }); + const findConfidentialBlock = (w = wrapper) => w.findComponent({ ref: 'confidential' }); + const findLockedAndConfidentialBlock = (w = wrapper) => + w.findComponent({ ref: 'lockedAndConfidential' }); const createComponent = (props) => shallowMount(NoteableWarning, { @@ -73,7 +74,7 @@ describe('Issue Warning Component', () => { }); it('renders warning icon', () => { - expect(wrapper.find(GlIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).exists()).toBe(true); }); it('does not render information about locked noteable', () => { @@ -99,7 +100,7 @@ describe('Issue Warning Component', () => { }); it('does not render warning icon', () => { - expect(wrapper.find(GlIcon).exists()).toBe(false); + expect(wrapper.findComponent(GlIcon).exists()).toBe(false); }); it('does not render information about locked noteable', () => { diff --git a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js index f951cfd5cd9..b86c8946e96 100644 --- a/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js +++ b/spec/frontend/vue_shared/components/notes/placeholder_note_spec.js @@ -14,7 +14,7 @@ const getters = { describe('Issue placeholder note component', () => { let wrapper; - const findNote = () => wrapper.find({ ref: 'note' }); + const findNote = () => wrapper.findComponent({ ref: 'note' }); const createComponent = (isIndividual = false, propsData = {}) => { wrapper = shallowMount(IssuePlaceholderNote, { diff --git a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js index 51a936c0509..c0c3c4a9729 100644 --- a/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js +++ b/spec/frontend/vue_shared/components/paginated_table_with_search_and_tabs/paginated_table_with_search_and_tabs_spec.js @@ -92,15 +92,15 @@ describe('AlertManagementEmptyState', () => { const EmptyState = () => wrapper.find('.empty-state'); const ItemsTable = () => wrapper.find('.gl-table'); - const ErrorAlert = () => wrapper.find(GlAlert); - const Pagination = () => wrapper.find(GlPagination); - const Tabs = () => wrapper.find(GlTabs); + const ErrorAlert = () => wrapper.findComponent(GlAlert); + const Pagination = () => wrapper.findComponent(GlPagination); + const Tabs = () => wrapper.findComponent(GlTabs); const ActionButton = () => wrapper.find('.header-actions > button'); - const Filters = () => wrapper.find(FilteredSearchBar); - const findPagination = () => wrapper.find(GlPagination); - const findStatusFilterTabs = () => wrapper.findAll(GlTab); - const findStatusTabs = () => wrapper.find(GlTabs); - const findStatusFilterBadge = () => wrapper.findAll(GlBadge); + const Filters = () => wrapper.findComponent(FilteredSearchBar); + const findPagination = () => wrapper.findComponent(GlPagination); + const findStatusFilterTabs = () => wrapper.findAllComponents(GlTab); + const findStatusTabs = () => wrapper.findComponent(GlTabs); + const findStatusFilterBadge = () => wrapper.findAllComponents(GlBadge); describe('Snowplow tracking', () => { beforeEach(() => { @@ -213,7 +213,7 @@ describe('AlertManagementEmptyState', () => { }); it('should render pagination', () => { - expect(wrapper.find(GlPagination).exists()).toBe(true); + expect(wrapper.findComponent(GlPagination).exists()).toBe(true); }); describe('prevPage', () => { diff --git a/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js b/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js index 08119dee8af..b3be2f8a775 100644 --- a/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js +++ b/spec/frontend/vue_shared/components/pagination_bar/pagination_bar_spec.js @@ -64,7 +64,7 @@ describe('Pagination bar', () => { }, }); - expect(wrapper.find(GlDropdown).find('button').text()).toMatchInterpolatedText( + expect(wrapper.findComponent(GlDropdown).find('button').text()).toMatchInterpolatedText( `${CURRENT_PAGE_SIZE} items per page`, ); }); diff --git a/spec/frontend/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js index 83f1e2844f9..d444ad7a733 100644 --- a/spec/frontend/vue_shared/components/pagination_links_spec.js +++ b/spec/frontend/vue_shared/components/pagination_links_spec.js @@ -41,7 +41,7 @@ describe('Pagination links component', () => { beforeEach(() => { createComponent(); - glPagination = wrapper.find(GlPagination); + glPagination = wrapper.findComponent(GlPagination); }); afterEach(() => { diff --git a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js index 379e60c1b2d..a0832dd7030 100644 --- a/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js +++ b/spec/frontend/vue_shared/components/project_selector/project_selector_spec.js @@ -15,7 +15,7 @@ describe('ProjectSelector component', () => { let selected = []; selected = selected.concat(allProjects.slice(0, 3)).concat(allProjects.slice(5, 8)); - const findSearchInput = () => wrapper.find(GlSearchBoxByType).find('input'); + const findSearchInput = () => wrapper.findComponent(GlSearchBoxByType).find('input'); const findLegendText = () => wrapper.find('[data-testid="legend-text"]').text(); const search = (query) => { const searchInput = findSearchInput(); @@ -65,14 +65,14 @@ describe('ProjectSelector component', () => { it(`triggers a "bottomReached" event when user has scrolled to the bottom of the list`, () => { jest.spyOn(vm, '$emit').mockImplementation(() => {}); - wrapper.find(GlInfiniteScroll).vm.$emit('bottomReached'); + wrapper.findComponent(GlInfiniteScroll).vm.$emit('bottomReached'); expect(vm.$emit).toHaveBeenCalledWith('bottomReached'); }); it(`triggers a "projectClicked" event when a project is clicked`, () => { jest.spyOn(vm, '$emit').mockImplementation(() => {}); - wrapper.find(ProjectListItem).vm.$emit('click', head(searchResults)); + wrapper.findComponent(ProjectListItem).vm.$emit('click', head(searchResults)); expect(vm.$emit).toHaveBeenCalledWith('projectClicked', head(searchResults)); }); diff --git a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js index 3a2ea263a05..8f19f0ea14d 100644 --- a/spec/frontend/vue_shared/components/registry/code_instruction_spec.js +++ b/spec/frontend/vue_shared/components/registry/code_instruction_spec.js @@ -22,7 +22,7 @@ describe('Package code instruction', () => { }); } - const findCopyButton = () => wrapper.find(ClipboardButton); + const findCopyButton = () => wrapper.findComponent(ClipboardButton); const findInputElement = () => wrapper.find('[data-testid="instruction-input"]'); const findMultilineInstruction = () => wrapper.find('[data-testid="multiline-instruction"]'); diff --git a/spec/frontend/vue_shared/components/registry/details_row_spec.js b/spec/frontend/vue_shared/components/registry/details_row_spec.js index 3134e0d3e21..ebc9816f983 100644 --- a/spec/frontend/vue_shared/components/registry/details_row_spec.js +++ b/spec/frontend/vue_shared/components/registry/details_row_spec.js @@ -5,7 +5,7 @@ import component from '~/vue_shared/components/registry/details_row.vue'; describe('DetailsRow', () => { let wrapper; - const findIcon = () => wrapper.find(GlIcon); + const findIcon = () => wrapper.findComponent(GlIcon); const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]'); const mountComponent = (props) => { diff --git a/spec/frontend/vue_shared/components/registry/history_item_spec.js b/spec/frontend/vue_shared/components/registry/history_item_spec.js index f146f87342f..947520567e6 100644 --- a/spec/frontend/vue_shared/components/registry/history_item_spec.js +++ b/spec/frontend/vue_shared/components/registry/history_item_spec.js @@ -27,8 +27,8 @@ describe('History Item', () => { wrapper = null; }); - const findTimelineEntry = () => wrapper.find(TimelineEntryItem); - const findGlIcon = () => wrapper.find(GlIcon); + const findTimelineEntry = () => wrapper.findComponent(TimelineEntryItem); + const findGlIcon = () => wrapper.findComponent(GlIcon); const findDefaultSlot = () => wrapper.find('[data-testid="default-slot"]'); const findBodySlot = () => wrapper.find('[data-testid="body-slot"]'); diff --git a/spec/frontend/vue_shared/components/registry/list_item_spec.js b/spec/frontend/vue_shared/components/registry/list_item_spec.js index 6e9abb2bfb3..b941eb77c32 100644 --- a/spec/frontend/vue_shared/components/registry/list_item_spec.js +++ b/spec/frontend/vue_shared/components/registry/list_item_spec.js @@ -13,7 +13,7 @@ describe('list item', () => { const findRightSecondarySlot = () => wrapper.find('[data-testid="right-secondary"]'); const findRightActionSlot = () => wrapper.find('[data-testid="right-action"]'); const findDetailsSlot = (name) => wrapper.find(`[data-testid="${name}"]`); - const findToggleDetailsButton = () => wrapper.find(GlButton); + const findToggleDetailsButton = () => wrapper.findComponent(GlButton); const mountComponent = (propsData, slots) => { wrapper = shallowMount(component, { diff --git a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js index e4abdc15fd5..a04e1e237d4 100644 --- a/spec/frontend/vue_shared/components/registry/metadata_item_spec.js +++ b/spec/frontend/vue_shared/components/registry/metadata_item_spec.js @@ -24,10 +24,10 @@ describe('Metadata Item', () => { wrapper = null; }); - const findIcon = () => wrapper.find(GlIcon); - const findLink = (w = wrapper) => w.find(GlLink); + const findIcon = () => wrapper.findComponent(GlIcon); + const findLink = (w = wrapper) => w.findComponent(GlLink); const findText = () => wrapper.find('[data-testid="metadata-item-text"]'); - const findTooltipOnTruncate = (w = wrapper) => w.find(TooltipOnTruncate); + const findTooltipOnTruncate = (w = wrapper) => w.findComponent(TooltipOnTruncate); const findTextTooltip = () => wrapper.find('[data-testid="text-tooltip-container"]'); describe.each(['xs', 's', 'm', 'l', 'xl'])('size class', (size) => { diff --git a/spec/frontend/vue_shared/components/registry/registry_search_spec.js b/spec/frontend/vue_shared/components/registry/registry_search_spec.js index 20716e79a04..70f4693ae81 100644 --- a/spec/frontend/vue_shared/components/registry/registry_search_spec.js +++ b/spec/frontend/vue_shared/components/registry/registry_search_spec.js @@ -6,9 +6,9 @@ import component from '~/vue_shared/components/registry/registry_search.vue'; describe('Registry Search', () => { let wrapper; - const findPackageListSorting = () => wrapper.find(GlSorting); - const findSortingItems = () => wrapper.findAll(GlSortingItem); - const findFilteredSearch = () => wrapper.find(GlFilteredSearch); + const findPackageListSorting = () => wrapper.findComponent(GlSorting); + const findSortingItems = () => wrapper.findAllComponents(GlSortingItem); + const findFilteredSearch = () => wrapper.findComponent(GlFilteredSearch); const defaultProps = { filters: [], diff --git a/spec/frontend/vue_shared/components/registry/title_area_spec.js b/spec/frontend/vue_shared/components/registry/title_area_spec.js index b62676b35be..efb57ddd310 100644 --- a/spec/frontend/vue_shared/components/registry/title_area_spec.js +++ b/spec/frontend/vue_shared/components/registry/title_area_spec.js @@ -199,7 +199,7 @@ describe('title area', () => { const message = findInfoMessages().at(0); - expect(message.find(GlLink).attributes('href')).toBe('bar'); + expect(message.findComponent(GlLink).attributes('href')).toBe('bar'); expect(message.text()).toBe('foo link'); }); diff --git a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js index a38dcd626f4..112a0e67a0f 100644 --- a/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js +++ b/spec/frontend/vue_shared/components/runner_instructions/runner_instructions_modal_spec.js @@ -166,7 +166,7 @@ describe('RunnerInstructionsModal component', () => { }); it('sets the focus on the default selected platform', () => { - const findOsxPlatformButton = () => wrapper.find({ ref: 'osx' }); + const findOsxPlatformButton = () => wrapper.findComponent({ ref: 'osx' }); findOsxPlatformButton().element.focus = jest.fn(); diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js index 71ebe561def..c5672bc28cc 100644 --- a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js +++ b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js @@ -50,7 +50,7 @@ describe('Merge request artifact Download', () => { return createMockApollo(requestHandlers); }; - const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown); + const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown); afterEach(() => { wrapper.destroy(); diff --git a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js index ae86106d86e..08d3d5b19d4 100644 --- a/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js +++ b/spec/frontend/vue_shared/components/security_reports/help_icon_spec.js @@ -17,9 +17,9 @@ describe('HelpIcon component', () => { }); }; - const findLink = () => wrapper.find(GlLink); - const findPopover = () => wrapper.find(GlPopover); - const findPopoverTarget = () => wrapper.find({ ref: 'discoverProjectSecurity' }); + const findLink = () => wrapper.findComponent(GlLink); + const findPopover = () => wrapper.findComponent(GlPopover); + const findPopoverTarget = () => wrapper.findComponent({ ref: 'discoverProjectSecurity' }); afterEach(() => { wrapper.destroy(); diff --git a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js index f213e37cbc1..a4e3e34713c 100644 --- a/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/issuable_move_dropdown_spec.js @@ -173,15 +173,15 @@ describe('IssuableMoveDropdown', () => { }); describe('template', () => { - const findDropdownEl = () => wrapper.find(GlDropdown); + const findDropdownEl = () => wrapper.findComponent(GlDropdown); it('renders collapsed state element with icon', () => { const collapsedEl = wrapper.find('[data-testid="move-collapsed"]'); expect(collapsedEl.exists()).toBe(true); expect(collapsedEl.attributes('title')).toBe(mockProps.dropdownButtonTitle); - expect(collapsedEl.find(GlIcon).exists()).toBe(true); - expect(collapsedEl.find(GlIcon).props('name')).toBe('arrow-right'); + expect(collapsedEl.findComponent(GlIcon).exists()).toBe(true); + expect(collapsedEl.findComponent(GlIcon).props('name')).toBe('arrow-right'); }); describe('gl-dropdown component', () => { @@ -191,7 +191,7 @@ describe('IssuableMoveDropdown', () => { }); it('renders gl-dropdown-form component', () => { - expect(findDropdownEl().find(GlDropdownForm).exists()).toBe(true); + expect(findDropdownEl().findComponent(GlDropdownForm).exists()).toBe(true); }); it('renders header element', () => { @@ -199,11 +199,11 @@ describe('IssuableMoveDropdown', () => { expect(headerEl.exists()).toBe(true); expect(headerEl.find('span').text()).toBe(mockProps.dropdownHeaderTitle); - expect(headerEl.find(GlButton).props('icon')).toBe('close'); + expect(headerEl.findComponent(GlButton).props('icon')).toBe('close'); }); it('renders gl-search-box-by-type component', () => { - const searchEl = findDropdownEl().find(GlSearchBoxByType); + const searchEl = findDropdownEl().findComponent(GlSearchBoxByType); expect(searchEl.exists()).toBe(true); expect(searchEl.attributes()).toMatchObject({ @@ -221,7 +221,7 @@ describe('IssuableMoveDropdown', () => { await nextTick(); - expect(findDropdownEl().find(GlLoadingIcon).exists()).toBe(true); + expect(findDropdownEl().findComponent(GlLoadingIcon).exists()).toBe(true); }); it('renders gl-dropdown-item components for available projects', async () => { @@ -234,7 +234,7 @@ describe('IssuableMoveDropdown', () => { await nextTick(); - const dropdownItems = wrapper.findAll(GlDropdownItem); + const dropdownItems = wrapper.findAllComponents(GlDropdownItem); expect(dropdownItems).toHaveLength(mockProjects.length); expect(dropdownItems.at(0).props()).toMatchObject({ @@ -285,7 +285,7 @@ describe('IssuableMoveDropdown', () => { }); it('renders gl-button within footer', async () => { - const moveButtonEl = wrapper.find('[data-testid="footer"]').find(GlButton); + const moveButtonEl = wrapper.find('[data-testid="footer"]').findComponent(GlButton); expect(moveButtonEl.text()).toBe('Move'); expect(moveButtonEl.attributes('disabled')).toBe('true'); @@ -299,7 +299,7 @@ describe('IssuableMoveDropdown', () => { await nextTick(); expect( - wrapper.find('[data-testid="footer"]').find(GlButton).attributes('disabled'), + wrapper.find('[data-testid="footer"]').findComponent(GlButton).attributes('disabled'), ).not.toBeDefined(); }); }); @@ -341,7 +341,7 @@ describe('IssuableMoveDropdown', () => { }); it('close icon in dropdown header closes the dropdown when clicked', () => { - wrapper.find('[data-testid="header"]').find(GlButton).vm.$emit('click', mockEvent); + wrapper.find('[data-testid="header"]').findComponent(GlButton).vm.$emit('click', mockEvent); expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled(); }); @@ -355,7 +355,7 @@ describe('IssuableMoveDropdown', () => { await nextTick(); - wrapper.findAll(GlDropdownItem).at(0).vm.$emit('click', mockEvent); + wrapper.findAllComponents(GlDropdownItem).at(0).vm.$emit('click', mockEvent); expect(wrapper.vm.selectedProject).toBe(mockProjects[0]); }); @@ -369,7 +369,7 @@ describe('IssuableMoveDropdown', () => { await nextTick(); - wrapper.find('[data-testid="footer"]').find(GlButton).vm.$emit('click'); + wrapper.find('[data-testid="footer"]').findComponent(GlButton).vm.$emit('click'); expect(wrapper.vm.$refs.dropdown.hide).toHaveBeenCalled(); expect(wrapper.emitted('move-issuable')).toBeTruthy(); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js index c05513a6d5f..c0e5408e1bd 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_button_spec.js @@ -33,9 +33,9 @@ describe('DropdownButton', () => { wrapper.destroy(); }); - const findDropdownButton = () => wrapper.find(GlButton); + const findDropdownButton = () => wrapper.findComponent(GlButton); const findDropdownText = () => wrapper.find('.dropdown-toggle-text'); - const findDropdownIcon = () => wrapper.find(GlIcon); + const findDropdownIcon = () => wrapper.findComponent(GlIcon); describe('methods', () => { describe('handleButtonClick', () => { @@ -61,7 +61,7 @@ describe('DropdownButton', () => { describe('template', () => { it('renders component container element', () => { - expect(wrapper.find(GlButton).element).toBe(wrapper.element); + expect(wrapper.findComponent(GlButton).element).toBe(wrapper.element); }); it('renders default button text element', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js index 0673ffee22b..799e2c1d08e 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_create_view_spec.js @@ -127,7 +127,7 @@ describe('DropdownContentsCreateView', () => { }); it('renders dropdown back button element', () => { - const backBtnEl = wrapper.find('.dropdown-title').findAll(GlButton).at(0); + const backBtnEl = wrapper.find('.dropdown-title').findAllComponents(GlButton).at(0); expect(backBtnEl.exists()).toBe(true); expect(backBtnEl.attributes('aria-label')).toBe('Go back'); @@ -142,7 +142,7 @@ describe('DropdownContentsCreateView', () => { }); it('renders dropdown close button element', () => { - const closeBtnEl = wrapper.find('.dropdown-title').findAll(GlButton).at(1); + const closeBtnEl = wrapper.find('.dropdown-title').findAllComponents(GlButton).at(1); expect(closeBtnEl.exists()).toBe(true); expect(closeBtnEl.attributes('aria-label')).toBe('Close'); @@ -150,7 +150,7 @@ describe('DropdownContentsCreateView', () => { }); it('renders label title input element', () => { - const titleInputEl = wrapper.find('.dropdown-input').find(GlFormInput); + const titleInputEl = wrapper.find('.dropdown-input').findComponent(GlFormInput); expect(titleInputEl.exists()).toBe(true); expect(titleInputEl.attributes('placeholder')).toBe('Name new label'); @@ -158,7 +158,7 @@ describe('DropdownContentsCreateView', () => { }); it('renders color block element for all suggested colors', () => { - const colorBlocksEl = wrapper.find('.dropdown-content').findAll(GlLink); + const colorBlocksEl = wrapper.find('.dropdown-content').findAllComponents(GlLink); colorBlocksEl.wrappers.forEach((colorBlock, index) => { expect(colorBlock.attributes('style')).toContain('background-color'); @@ -175,7 +175,7 @@ describe('DropdownContentsCreateView', () => { await nextTick(); const colorPreviewEl = wrapper.find('.color-input-container > .dropdown-label-color-preview'); - const colorInputEl = wrapper.find('.color-input-container').find(GlFormInput); + const colorInputEl = wrapper.find('.color-input-container').findComponent(GlFormInput); expect(colorPreviewEl.exists()).toBe(true); expect(colorPreviewEl.attributes('style')).toContain('background-color'); @@ -185,7 +185,7 @@ describe('DropdownContentsCreateView', () => { }); it('renders create button element', () => { - const createBtnEl = wrapper.find('.dropdown-actions').findAll(GlButton).at(0); + const createBtnEl = wrapper.find('.dropdown-actions').findAllComponents(GlButton).at(0); expect(createBtnEl.exists()).toBe(true); expect(createBtnEl.text()).toContain('Create'); @@ -195,14 +195,14 @@ describe('DropdownContentsCreateView', () => { wrapper.vm.$store.dispatch('requestCreateLabel'); await nextTick(); - const loadingIconEl = wrapper.find('.dropdown-actions').find(GlLoadingIcon); + const loadingIconEl = wrapper.find('.dropdown-actions').findComponent(GlLoadingIcon); expect(loadingIconEl.exists()).toBe(true); expect(loadingIconEl.isVisible()).toBe(true); }); it('renders cancel button element', () => { - const cancelBtnEl = wrapper.find('.dropdown-actions').findAll(GlButton).at(1); + const cancelBtnEl = wrapper.find('.dropdown-actions').findAllComponents(GlButton).at(1); expect(cancelBtnEl.exists()).toBe(true); expect(cancelBtnEl.text()).toContain('Cancel'); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js index 00c8e3a814a..cc9b9f393ce 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_contents_labels_view_spec.js @@ -58,7 +58,7 @@ describe('DropdownContentsLabelsView', () => { const findDropdownContent = () => wrapper.find('[data-testid="dropdown-content"]'); const findDropdownTitle = () => wrapper.find('[data-testid="dropdown-title"]'); const findDropdownFooter = () => wrapper.find('[data-testid="dropdown-footer"]'); - const findLoadingIcon = () => wrapper.find(GlLoadingIcon); + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); describe('computed', () => { describe('visibleLabels', () => { @@ -285,7 +285,7 @@ describe('DropdownContentsLabelsView', () => { describe('template', () => { it('renders gl-intersection-observer as component root', () => { - expect(wrapper.find(GlIntersectionObserver).exists()).toBe(true); + expect(wrapper.findComponent(GlIntersectionObserver).exists()).toBe(true); }); it('renders gl-loading-icon component when `labelsFetchInProgress` prop is true', async () => { @@ -316,20 +316,20 @@ describe('DropdownContentsLabelsView', () => { }); it('renders dropdown close button element', () => { - const closeButtonEl = findDropdownTitle().find(GlButton); + const closeButtonEl = findDropdownTitle().findComponent(GlButton); expect(closeButtonEl.exists()).toBe(true); expect(closeButtonEl.props('icon')).toBe('close'); }); it('renders label search input element', () => { - const searchInputEl = wrapper.find(GlSearchBoxByType); + const searchInputEl = wrapper.findComponent(GlSearchBoxByType); expect(searchInputEl.exists()).toBe(true); }); it('renders label elements for all labels', () => { - expect(wrapper.findAll(LabelItem)).toHaveLength(mockLabels.length); + expect(wrapper.findAllComponents(LabelItem)).toHaveLength(mockLabels.length); }); it('renders label element with `highlight` set to true when value of `currentHighlightItem` is more than -1', async () => { @@ -340,7 +340,7 @@ describe('DropdownContentsLabelsView', () => { }); await nextTick(); - const labelItemEl = findDropdownContent().find(LabelItem); + const labelItemEl = findDropdownContent().findComponent(LabelItem); expect(labelItemEl.attributes('highlight')).toBe('true'); }); @@ -373,7 +373,7 @@ describe('DropdownContentsLabelsView', () => { }); it('renders footer list items', () => { - const footerLinks = findDropdownFooter().findAll(GlLink); + const footerLinks = findDropdownFooter().findAllComponents(GlLink); const createLabelLink = footerLinks.at(0); const manageLabelsLink = footerLinks.at(1); @@ -387,7 +387,7 @@ describe('DropdownContentsLabelsView', () => { wrapper.vm.$store.state.allowLabelCreate = false; await nextTick(); - const createLabelLink = findDropdownFooter().findAll(GlLink).at(0); + const createLabelLink = findDropdownFooter().findAllComponents(GlLink).at(0); expect(createLabelLink.text()).not.toBe('Create label'); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js index 84e9f3f41c3..54804f85f81 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/dropdown_title_spec.js @@ -41,7 +41,7 @@ describe('DropdownTitle', () => { }); it('renders edit link', () => { - const editBtnEl = wrapper.find(GlButton); + const editBtnEl = wrapper.findComponent(GlButton); expect(editBtnEl.exists()).toBe(true); expect(editBtnEl.text()).toBe('Edit'); @@ -53,7 +53,7 @@ describe('DropdownTitle', () => { }); await nextTick(); - expect(wrapper.find(GlLoadingIcon).isVisible()).toBe(true); + expect(wrapper.findComponent(GlLoadingIcon).isVisible()).toBe(true); }); }); }); diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js index bedb6204088..bb0f1777de6 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/label_item_spec.js @@ -32,7 +32,7 @@ describe('LabelItem', () => { describe('template', () => { it('renders gl-link component', () => { - expect(wrapper.find(GlLink).exists()).toBe(true); + expect(wrapper.findComponent(GlLink).exists()).toBe(true); }); it('renders component root with class `is-focused` when `highlight` prop is true', () => { diff --git a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js index c150410ff8e..4c7ac6e9a6f 100644 --- a/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/labels_select_vue/labels_select_root_spec.js @@ -138,13 +138,13 @@ describe('LabelsSelectRoot', () => { it('renders `dropdown-value-collapsed` component when `allowLabelCreate` prop is `true`', async () => { createComponent(); await nextTick(); - expect(wrapper.find(DropdownValueCollapsed).exists()).toBe(true); + expect(wrapper.findComponent(DropdownValueCollapsed).exists()).toBe(true); }); it('renders `dropdown-title` component', async () => { createComponent(); await nextTick(); - expect(wrapper.find(DropdownTitle).exists()).toBe(true); + expect(wrapper.findComponent(DropdownTitle).exists()).toBe(true); }); it('renders `dropdown-value` component', async () => { @@ -153,7 +153,7 @@ describe('LabelsSelectRoot', () => { }); await nextTick(); - const valueComp = wrapper.find(DropdownValue); + const valueComp = wrapper.findComponent(DropdownValue); expect(valueComp.exists()).toBe(true); expect(valueComp.text()).toBe('None'); @@ -163,14 +163,14 @@ describe('LabelsSelectRoot', () => { createComponent(); wrapper.vm.$store.dispatch('toggleDropdownButton'); await nextTick(); - expect(wrapper.find(DropdownButton).exists()).toBe(true); + expect(wrapper.findComponent(DropdownButton).exists()).toBe(true); }); it('renders `dropdown-contents` component when `showDropdownButton` & `showDropdownContents` prop is `true`', async () => { createComponent(); wrapper.vm.$store.dispatch('toggleDropdownContents'); await nextTick(); - expect(wrapper.find(DropdownContents).exists()).toBe(true); + expect(wrapper.findComponent(DropdownContents).exists()).toBe(true); }); describe('sets content direction based on viewport', () => { @@ -187,7 +187,7 @@ describe('LabelsSelectRoot', () => { wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); await nextTick(); - expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(true); + expect(wrapper.findComponent(DropdownContents).props('renderOnTop')).toBe(true); }); it('does not set direction when inside of viewport', async () => { @@ -195,7 +195,7 @@ describe('LabelsSelectRoot', () => { wrapper.vm.setContentIsOnViewport(wrapper.vm.$store.state); await nextTick(); - expect(wrapper.find(DropdownContents).props('renderOnTop')).toBe(false); + expect(wrapper.findComponent(DropdownContents).props('renderOnTop')).toBe(false); }); }, ); diff --git a/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js b/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js index de3e1ccfb03..853cc4a6331 100644 --- a/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js +++ b/spec/frontend/vue_shared/components/sidebar/todo_button_spec.js @@ -30,19 +30,19 @@ describe('Todo Button', () => { it('renders GlButton', () => { createComponent(); - expect(wrapper.find(GlButton).exists()).toBe(true); + expect(wrapper.findComponent(GlButton).exists()).toBe(true); }); it('emits click event when clicked', () => { createComponent({}, mount); - wrapper.find(GlButton).trigger('click'); + wrapper.findComponent(GlButton).trigger('click'); expect(wrapper.emitted().click).toBeTruthy(); }); it('calls dispatchDocumentEvent to update global To-Do counter correctly', () => { createComponent({}, mount); - wrapper.find(GlButton).trigger('click'); + wrapper.findComponent(GlButton).trigger('click'); const dispatchedEvent = dispatchEventSpy.mock.calls[0][0]; expect(dispatchEventSpy).toHaveBeenCalledTimes(1); @@ -57,12 +57,12 @@ describe('Todo Button', () => { `('sets correct label when isTodo is $isTodo', ({ label, isTodo }) => { createComponent({ isTodo }); - expect(wrapper.find(GlButton).text()).toBe(label); + expect(wrapper.findComponent(GlButton).text()).toBe(label); }); it('binds additional props to GlButton', () => { createComponent({ loading: true }); - expect(wrapper.find(GlButton).props('loading')).toBe(true); + expect(wrapper.findComponent(GlButton).props('loading')).toBe(true); }); }); diff --git a/spec/frontend/vue_shared/components/source_editor_spec.js b/spec/frontend/vue_shared/components/source_editor_spec.js index dca4d60e23c..9bc91c99dd9 100644 --- a/spec/frontend/vue_shared/components/source_editor_spec.js +++ b/spec/frontend/vue_shared/components/source_editor_spec.js @@ -77,7 +77,7 @@ describe('Source Editor component', () => { }); it('initialises Source Editor instance', () => { - const el = wrapper.find({ ref: 'editor' }).element; + const el = wrapper.findComponent({ ref: 'editor' }).element; expect(createInstanceMock).toHaveBeenCalledWith({ el, blobPath: fileName, @@ -112,7 +112,7 @@ describe('Source Editor component', () => { }); it('emits EDITOR_READY_EVENT event when the Source Editor is ready', async () => { - const el = wrapper.find({ ref: 'editor' }).element; + const el = wrapper.findComponent({ ref: 'editor' }).element; expect(wrapper.emitted()[EDITOR_READY_EVENT]).toBeUndefined(); await el.dispatchEvent(new Event(EDITOR_READY_EVENT)); diff --git a/spec/frontend/vue_shared/components/split_button_spec.js b/spec/frontend/vue_shared/components/split_button_spec.js index 4965969bc3e..6b869db4058 100644 --- a/spec/frontend/vue_shared/components/split_button_spec.js +++ b/spec/frontend/vue_shared/components/split_button_spec.js @@ -26,8 +26,9 @@ describe('SplitButton', () => { }); }; - const findDropdown = () => wrapper.find(GlDropdown); - const findDropdownItem = (index = 0) => findDropdown().findAll(GlDropdownItem).at(index); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItem = (index = 0) => + findDropdown().findAllComponents(GlDropdownItem).at(index); const selectItem = async (index) => { findDropdownItem(index).vm.$emit('click'); diff --git a/spec/frontend/vue_shared/components/table_pagination_spec.js b/spec/frontend/vue_shared/components/table_pagination_spec.js index ed23a47c328..99de26ce2ae 100644 --- a/spec/frontend/vue_shared/components/table_pagination_spec.js +++ b/spec/frontend/vue_shared/components/table_pagination_spec.js @@ -50,7 +50,7 @@ describe('Pagination component', () => { change: spy, }); - expect(wrapper.find(GlPagination).exists()).toBe(true); + expect(wrapper.findComponent(GlPagination).exists()).toBe(true); }); it('renders if there is a prev page', () => { @@ -66,7 +66,7 @@ describe('Pagination component', () => { change: spy, }); - expect(wrapper.find(GlPagination).exists()).toBe(true); + expect(wrapper.findComponent(GlPagination).exists()).toBe(true); }); }); @@ -83,7 +83,7 @@ describe('Pagination component', () => { }, change: spy, }); - wrapper.find(GlPagination).vm.$emit('input', 3); + wrapper.findComponent(GlPagination).vm.$emit('input', 3); expect(spy).toHaveBeenCalledWith(3); }); }); diff --git a/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js index 9e7e5c1263f..ca1f7996ad6 100644 --- a/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js +++ b/spec/frontend/vue_shared/components/tooltip_on_truncate_spec.js @@ -68,7 +68,7 @@ describe('TooltipOnTruncate component', () => { }, ); - wrapper = parent.find(WrappedTooltipOnTruncate); + wrapper = parent.findComponent(WrappedTooltipOnTruncate); }; const getTooltipValue = () => getBinding(wrapper.element, 'gl-tooltip')?.value; diff --git a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js index 21e9b401215..a063a5591e3 100644 --- a/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js +++ b/spec/frontend/vue_shared/components/upload_dropzone/upload_dropzone_spec.js @@ -14,7 +14,7 @@ describe('Upload dropzone component', () => { const findDropzoneCard = () => wrapper.find('.upload-dropzone-card'); const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]'); - const findIcon = () => wrapper.find(GlIcon); + const findIcon = () => wrapper.findComponent(GlIcon); const findUploadText = () => wrapper.find('[data-testid="upload-text"]').text(); const findFileInput = () => wrapper.find('input[type="file"]'); diff --git a/spec/frontend/vue_shared/components/user_access_role_badge_spec.js b/spec/frontend/vue_shared/components/user_access_role_badge_spec.js index 7f25f7c08e7..cea6fcac8c8 100644 --- a/spec/frontend/vue_shared/components/user_access_role_badge_spec.js +++ b/spec/frontend/vue_shared/components/user_access_role_badge_spec.js @@ -18,7 +18,7 @@ describe('UserAccessRoleBadge', () => { }, }); - const badge = wrapper.find(GlBadge); + const badge = wrapper.findComponent(GlBadge); expect(badge.exists()).toBe(true); expect(badge.html()).toContain('test slot content'); diff --git a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js index 20ff0848cff..b9accbf0373 100644 --- a/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js +++ b/spec/frontend/vue_shared/components/user_avatar/user_avatar_list_spec.js @@ -38,7 +38,7 @@ describe('UserAvatarList', () => { }; const clickButton = () => { - const button = wrapper.find(GlButton); + const button = wrapper.findComponent(GlButton); button.vm.$emit('click'); }; @@ -79,7 +79,7 @@ describe('UserAvatarList', () => { const items = createList(20); factory({ propsData: { items } }); - const links = wrapper.findAll(UserAvatarLink); + const links = wrapper.findAllComponents(UserAvatarLink); const linkProps = links.wrappers.map((x) => x.props()); expect(linkProps).toEqual( @@ -105,7 +105,7 @@ describe('UserAvatarList', () => { it('renders all avatars if length is <= breakpoint', () => { factory(); - const links = wrapper.findAll(UserAvatarLink); + const links = wrapper.findAllComponents(UserAvatarLink); expect(links.length).toEqual(props.items.length); }); @@ -113,7 +113,7 @@ describe('UserAvatarList', () => { it('does not show button', () => { factory(); - expect(wrapper.find(GlButton).exists()).toBe(false); + expect(wrapper.findComponent(GlButton).exists()).toBe(false); }); }); @@ -126,7 +126,7 @@ describe('UserAvatarList', () => { it('renders avatars up to breakpoint', () => { factory(); - const links = wrapper.findAll(UserAvatarLink); + const links = wrapper.findAllComponents(UserAvatarLink); expect(links.length).toEqual(TEST_BREAKPOINT); }); @@ -138,7 +138,7 @@ describe('UserAvatarList', () => { }); it('renders all avatars', () => { - const links = wrapper.findAll(UserAvatarLink); + const links = wrapper.findAllComponents(UserAvatarLink); expect(links.length).toEqual(props.items.length); }); @@ -147,7 +147,7 @@ describe('UserAvatarList', () => { clickButton(); await nextTick(); - const links = wrapper.findAll(UserAvatarLink); + const links = wrapper.findAllComponents(UserAvatarLink); expect(links.length).toEqual(TEST_BREAKPOINT); }); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index 9aede4ab28a..7bac9f8235b 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -88,7 +88,7 @@ describe('User Popover Component', () => { }, }); - expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true); + expect(wrapper.findComponent(GlSkeletonLoader).exists()).toBe(true); }); }); @@ -102,7 +102,7 @@ describe('User Popover Component', () => { it('shows icon for location', () => { createWrapper(); - const iconEl = wrapper.find(GlIcon); + const iconEl = wrapper.findComponent(GlIcon); expect(iconEl.props('name')).toEqual('location'); }); @@ -115,8 +115,8 @@ describe('User Popover Component', () => { }); describe('job data', () => { - const findWorkInformation = () => wrapper.find({ ref: 'workInformation' }); - const findBio = () => wrapper.find({ ref: 'bio' }); + const findWorkInformation = () => wrapper.findComponent({ ref: 'workInformation' }); + const findBio = () => wrapper.findComponent({ ref: 'bio' }); const bio = 'My super interesting bio'; it('should show only bio if work information is not available', () => { @@ -172,7 +172,7 @@ describe('User Popover Component', () => { createWrapper({ user }); expect( - wrapper.findAll(GlIcon).filter((icon) => icon.props('name') === 'profile').length, + wrapper.findAllComponents(GlIcon).filter((icon) => icon.props('name') === 'profile').length, ).toEqual(1); }); @@ -185,7 +185,7 @@ describe('User Popover Component', () => { createWrapper({ user }); expect( - wrapper.findAll(GlIcon).filter((icon) => icon.props('name') === 'work').length, + wrapper.findAllComponents(GlIcon).filter((icon) => icon.props('name') === 'work').length, ).toEqual(1); }); }); diff --git a/spec/frontend/vue_shared/components/web_ide_link_spec.js b/spec/frontend/vue_shared/components/web_ide_link_spec.js index 040461f6be4..41163aef320 100644 --- a/spec/frontend/vue_shared/components/web_ide_link_spec.js +++ b/spec/frontend/vue_shared/components/web_ide_link_spec.js @@ -108,8 +108,8 @@ describe('Web IDE link component', () => { wrapper.destroy(); }); - const findActionsButton = () => wrapper.find(ActionsButton); - const findLocalStorageSync = () => wrapper.find(LocalStorageSync); + const findActionsButton = () => wrapper.findComponent(ActionsButton); + const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync); const findModal = () => wrapper.findComponent(GlModal); const findForkConfirmModal = () => wrapper.findComponent(ConfirmForkModal); diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js index 81362edaf37..7b0f0f7e344 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_create_root_spec.js @@ -51,11 +51,11 @@ describe('IssuableCreateRoot', () => { }); it('renders issuable-form component', () => { - expect(wrapper.find(IssuableForm).exists()).toBe(true); + expect(wrapper.findComponent(IssuableForm).exists()).toBe(true); }); it('renders contents for slot "actions" within issuable-form component', () => { - const buttonEl = wrapper.find(IssuableForm).find('button.js-issuable-save'); + const buttonEl = wrapper.findComponent(IssuableForm).find('button.js-issuable-save'); expect(buttonEl.exists()).toBe(true); expect(buttonEl.text()).toBe('Submit issuable'); diff --git a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js index cbfd05e7903..f98e7a678f4 100644 --- a/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js +++ b/spec/frontend/vue_shared/issuable/create/components/issuable_form_spec.js @@ -65,9 +65,9 @@ describe('IssuableForm', () => { expect(titleFieldEl.exists()).toBe(true); expect(titleFieldEl.find('label').text()).toBe('Title'); - expect(titleFieldEl.find(GlFormInput).exists()).toBe(true); - expect(titleFieldEl.find(GlFormInput).attributes('placeholder')).toBe('Title'); - expect(titleFieldEl.find(GlFormInput).attributes('autofocus')).toBe('true'); + expect(titleFieldEl.findComponent(GlFormInput).exists()).toBe(true); + expect(titleFieldEl.findComponent(GlFormInput).attributes('placeholder')).toBe('Title'); + expect(titleFieldEl.findComponent(GlFormInput).attributes('autofocus')).toBe('true'); }); it('renders issuable description input field', () => { @@ -75,8 +75,8 @@ describe('IssuableForm', () => { expect(descriptionFieldEl.exists()).toBe(true); expect(descriptionFieldEl.find('label').text()).toBe('Description'); - expect(descriptionFieldEl.find(MarkdownField).exists()).toBe(true); - expect(descriptionFieldEl.find(MarkdownField).props()).toMatchObject({ + expect(descriptionFieldEl.findComponent(MarkdownField).exists()).toBe(true); + expect(descriptionFieldEl.findComponent(MarkdownField).props()).toMatchObject({ markdownPreviewPath: wrapper.vm.descriptionPreviewPath, markdownDocsPath: wrapper.vm.descriptionHelpPath, addSpacingClasses: false, @@ -94,8 +94,8 @@ describe('IssuableForm', () => { expect(labelsSelectEl.exists()).toBe(true); expect(labelsSelectEl.find('label').text()).toBe('Labels'); - expect(labelsSelectEl.find(LabelsSelect).exists()).toBe(true); - expect(labelsSelectEl.find(LabelsSelect).props()).toMatchObject({ + expect(labelsSelectEl.findComponent(LabelsSelect).exists()).toBe(true); + expect(labelsSelectEl.findComponent(LabelsSelect).props()).toMatchObject({ allowLabelEdit: true, allowLabelCreate: true, allowMultiselect: true, diff --git a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js index 80f14dffd08..4a1dc3204e1 100644 --- a/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js +++ b/spec/frontend/vue_shared/issuable/list/components/issuable_item_spec.js @@ -273,9 +273,9 @@ describe('IssuableItem', () => { const titleEl = wrapper.find('[data-testid="issuable-title"]'); expect(titleEl.exists()).toBe(true); - expect(titleEl.find(GlLink).attributes('href')).toBe(expectedHref); - expect(titleEl.find(GlLink).attributes('target')).toBe(expectedTarget); - expect(titleEl.find(GlLink).text()).toBe(mockIssuable.title); + expect(titleEl.findComponent(GlLink).attributes('href')).toBe(expectedHref); + expect(titleEl.findComponent(GlLink).attributes('target')).toBe(expectedTarget); + expect(titleEl.findComponent(GlLink).text()).toBe(mockIssuable.title); }, ); @@ -286,8 +286,8 @@ describe('IssuableItem', () => { await nextTick(); - expect(wrapper.find(GlFormCheckbox).exists()).toBe(true); - expect(wrapper.find(GlFormCheckbox).attributes('checked')).not.toBeDefined(); + expect(wrapper.findComponent(GlFormCheckbox).exists()).toBe(true); + expect(wrapper.findComponent(GlFormCheckbox).attributes('checked')).not.toBeDefined(); wrapper.setProps({ checked: true, @@ -295,7 +295,7 @@ describe('IssuableItem', () => { await nextTick(); - expect(wrapper.find(GlFormCheckbox).attributes('checked')).toBe('true'); + expect(wrapper.findComponent(GlFormCheckbox).attributes('checked')).toBe('true'); }); it('renders issuable title with `target` set as "_blank" when issuable.webUrl is external', async () => { @@ -308,9 +308,9 @@ describe('IssuableItem', () => { await nextTick(); - expect(wrapper.find('[data-testid="issuable-title"]').find(GlLink).attributes('target')).toBe( - '_blank', - ); + expect( + wrapper.find('[data-testid="issuable-title"]').findComponent(GlLink).attributes('target'), + ).toBe('_blank'); }); it('renders issuable confidential icon when issuable is confidential', async () => { @@ -323,7 +323,7 @@ describe('IssuableItem', () => { await nextTick(); - const confidentialEl = wrapper.find('[data-testid="issuable-title"]').find(GlIcon); + const confidentialEl = wrapper.find('[data-testid="issuable-title"]').findComponent(GlIcon); expect(confidentialEl.exists()).toBe(true); expect(confidentialEl.props('name')).toBe('eye-slash'); @@ -440,7 +440,7 @@ describe('IssuableItem', () => { it('renders gl-label component for each label present within `issuable` prop', () => { wrapper = createComponent(); - const labelsEl = wrapper.findAll(GlLabel); + const labelsEl = wrapper.findAllComponents(GlLabel); expect(labelsEl.exists()).toBe(true); expect(labelsEl).toHaveLength(mockLabels.length); @@ -476,18 +476,18 @@ describe('IssuableItem', () => { const discussionsEl = wrapper.find('[data-testid="issuable-discussions"]'); expect(discussionsEl.exists()).toBe(true); - expect(discussionsEl.find(GlLink).attributes()).toMatchObject({ + expect(discussionsEl.findComponent(GlLink).attributes()).toMatchObject({ title: 'Comments', href: `${mockIssuable.webUrl}#notes`, }); - expect(discussionsEl.find(GlIcon).props('name')).toBe('comments'); - expect(discussionsEl.find(GlLink).text()).toContain('2'); + expect(discussionsEl.findComponent(GlIcon).props('name')).toBe('comments'); + expect(discussionsEl.findComponent(GlLink).text()).toContain('2'); }); it('renders issuable-assignees component', () => { wrapper = createComponent(); - const assigneesEl = wrapper.find(IssuableAssignees); + const assigneesEl = wrapper.findComponent(IssuableAssignees); expect(assigneesEl.exists()).toBe(true); expect(assigneesEl.props()).toMatchObject({ diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js index 7c582360637..39a76a51191 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_body_spec.js @@ -154,7 +154,7 @@ describe('IssuableBody', () => { describe('template', () => { it('renders issuable-title component', () => { - const titleEl = wrapper.find(IssuableTitle); + const titleEl = wrapper.findComponent(IssuableTitle); expect(titleEl.exists()).toBe(true); expect(titleEl.props()).toMatchObject({ @@ -165,7 +165,7 @@ describe('IssuableBody', () => { }); it('renders issuable-description component', () => { - const descriptionEl = wrapper.find(IssuableDescription); + const descriptionEl = wrapper.findComponent(IssuableDescription); expect(descriptionEl.exists()).toBe(true); expect(descriptionEl.props('issuable')).toEqual(issuableBodyProps.issuable); @@ -184,7 +184,7 @@ describe('IssuableBody', () => { await nextTick(); - const editFormEl = wrapper.find(IssuableEditForm); + const editFormEl = wrapper.findComponent(IssuableEditForm); expect(editFormEl.exists()).toBe(true); expect(editFormEl.props()).toMatchObject({ issuable: issuableBodyProps.issuable, @@ -198,7 +198,7 @@ describe('IssuableBody', () => { describe('events', () => { it('component emits `edit-issuable` event bubbled via issuable-title', () => { - const issuableTitle = wrapper.find(IssuableTitle); + const issuableTitle = wrapper.findComponent(IssuableTitle); issuableTitle.vm.$emit('edit-issuable'); @@ -223,7 +223,7 @@ describe('IssuableBody', () => { await nextTick(); - const issuableEditForm = wrapper.find(IssuableEditForm); + const issuableEditForm = wrapper.findComponent(IssuableEditForm); issuableEditForm.vm.$emit(eventName, eventObj, issuableMeta); diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js index d3e484cf913..d843da4da5b 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_edit_form_spec.js @@ -124,7 +124,7 @@ describe('IssuableEditForm', () => { const titleInputEl = wrapper.find('[data-testid="title"]'); expect(titleInputEl.exists()).toBe(true); - expect(titleInputEl.find(GlFormInput).attributes()).toMatchObject({ + expect(titleInputEl.findComponent(GlFormInput).attributes()).toMatchObject({ 'aria-label': 'Title', placeholder: 'Title', }); @@ -134,7 +134,7 @@ describe('IssuableEditForm', () => { const descriptionEl = wrapper.find('[data-testid="description"]'); expect(descriptionEl.exists()).toBe(true); - expect(descriptionEl.find(MarkdownField).props()).toMatchObject({ + expect(descriptionEl.findComponent(MarkdownField).props()).toMatchObject({ markdownPreviewPath: issuableEditFormProps.descriptionPreviewPath, markdownDocsPath: issuableEditFormProps.descriptionHelpPath, enableAutocomplete: issuableEditFormProps.enableAutocomplete, @@ -161,7 +161,7 @@ describe('IssuableEditForm', () => { }; it('component emits `keydown-title` event with event object and issuableMeta params via gl-form-input', async () => { - const titleInputEl = wrapper.find(GlFormInput); + const titleInputEl = wrapper.findComponent(GlFormInput); titleInputEl.vm.$emit('keydown', eventObj, 'title'); diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js index e00bb184535..fe3b05b3378 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_header_spec.js @@ -86,7 +86,7 @@ describe('IssuableHeader', () => { const blockedEl = wrapper.findByTestId('blocked'); expect(blockedEl.exists()).toBe(true); - expect(blockedEl.find(GlIcon).props('name')).toBe('lock'); + expect(blockedEl.findComponent(GlIcon).props('name')).toBe('lock'); }); it('renders confidential icon when issuable is confidential', async () => { @@ -97,7 +97,7 @@ describe('IssuableHeader', () => { const confidentialEl = wrapper.findByTestId('confidential'); expect(confidentialEl.exists()).toBe(true); - expect(confidentialEl.find(GlIcon).props('name')).toBe('eye-slash'); + expect(confidentialEl.findComponent(GlIcon).props('name')).toBe('eye-slash'); }); it('renders issuable author avatar', () => { @@ -113,12 +113,12 @@ describe('IssuableHeader', () => { const avatarEl = wrapper.findByTestId('avatar'); expect(avatarEl.exists()).toBe(true); expect(avatarEl.attributes()).toMatchObject(avatarElAttrs); - expect(avatarEl.find(GlAvatarLabeled).attributes()).toMatchObject({ + expect(avatarEl.findComponent(GlAvatarLabeled).attributes()).toMatchObject({ size: '24', src: avatarUrl, label: name, }); - expect(avatarEl.find(GlAvatarLabeled).find(GlIcon).exists()).toBe(false); + expect(avatarEl.findComponent(GlAvatarLabeled).findComponent(GlIcon).exists()).toBe(false); }); it('renders task status text when `taskCompletionStatus` prop is defined', () => { @@ -172,7 +172,7 @@ describe('IssuableHeader', () => { ); const avatarEl = wrapper.findComponent(GlAvatarLabeled); - const icon = avatarEl.find(GlIcon); + const icon = avatarEl.findComponent(GlIcon); expect(icon.exists()).toBe(true); expect(icon.props('name')).toBe('external-link'); diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js index f56064ed8e1..5d01ced3d30 100644 --- a/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js +++ b/spec/frontend/vue_shared/issuable/show/components/issuable_show_root_spec.js @@ -63,7 +63,7 @@ describe('IssuableShowRoot', () => { }); it('renders issuable-header component', () => { - const issuableHeader = wrapper.find(IssuableHeader); + const issuableHeader = wrapper.findComponent(IssuableHeader); expect(issuableHeader.exists()).toBe(true); expect(issuableHeader.props()).toMatchObject({ @@ -84,7 +84,7 @@ describe('IssuableShowRoot', () => { }); it('renders issuable-body component', () => { - const issuableBody = wrapper.find(IssuableBody); + const issuableBody = wrapper.findComponent(IssuableBody); expect(issuableBody.exists()).toBe(true); expect(issuableBody.props()).toMatchObject({ @@ -99,14 +99,14 @@ describe('IssuableShowRoot', () => { }); it('renders issuable-sidebar component', () => { - const issuableSidebar = wrapper.find(IssuableSidebar); + const issuableSidebar = wrapper.findComponent(IssuableSidebar); expect(issuableSidebar.exists()).toBe(true); }); describe('events', () => { it('component emits `edit-issuable` event bubbled via issuable-body', () => { - const issuableBody = wrapper.find(IssuableBody); + const issuableBody = wrapper.findComponent(IssuableBody); issuableBody.vm.$emit('edit-issuable'); @@ -114,7 +114,7 @@ describe('IssuableShowRoot', () => { }); it('component emits `task-list-update-success` event bubbled via issuable-body', () => { - const issuableBody = wrapper.find(IssuableBody); + const issuableBody = wrapper.findComponent(IssuableBody); const eventParam = { foo: 'bar', }; @@ -126,7 +126,7 @@ describe('IssuableShowRoot', () => { }); it('component emits `task-list-update-failure` event bubbled via issuable-body', () => { - const issuableBody = wrapper.find(IssuableBody); + const issuableBody = wrapper.findComponent(IssuableBody); issuableBody.vm.$emit('task-list-update-failure'); @@ -145,7 +145,7 @@ describe('IssuableShowRoot', () => { issuableDescription: 'foobar', }; - const issuableBody = wrapper.find(IssuableBody); + const issuableBody = wrapper.findComponent(IssuableBody); issuableBody.vm.$emit(eventName, eventObj, issuableMeta); diff --git a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js index 4b75da0b126..5f2b13a79c9 100644 --- a/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js +++ b/spec/frontend/vue_shared/security_reports/components/security_report_download_dropdown_spec.js @@ -12,8 +12,8 @@ describe('SecurityReportDownloadDropdown component', () => { }); }; - const findDropdown = () => wrapper.find(GlDropdown); - const findDropdownItems = () => wrapper.findAll(GlDropdownItem); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); afterEach(() => { wrapper.destroy(); diff --git a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js index 68a97103d3a..a9651cf8bac 100644 --- a/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js +++ b/spec/frontend/vue_shared/security_reports/security_reports_app_spec.js @@ -70,8 +70,8 @@ describe('Security reports app', () => { return createMockApollo(requestHandlers); }; - const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown); - const findHelpIconComponent = () => wrapper.find(HelpIcon); + const findDownloadDropdown = () => wrapper.findComponent(SecurityReportDownloadDropdown); + const findHelpIconComponent = () => wrapper.findComponent(HelpIcon); afterEach(() => { wrapper.destroy(); |