From cddf2db96b2280ad995b589b70ff23ff77cceb7b Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 27 Sep 2021 12:13:56 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../ide/components/preview/navigator.vue | 2 +- .../components/additional_metadata.vue | 94 --------- .../packages/details/components/app.vue | 68 +------ .../components/composer_installation.vue | 65 ------ .../details/components/conan_installation.vue | 59 ------ .../details/components/dependency_row.vue | 35 ---- .../components/installation_commands.vue | 55 ----- .../details/components/installation_title.vue | 38 ---- .../details/components/maven_installation.vue | 153 -------------- .../details/components/npm_installation.vue | 103 ---------- .../details/components/nuget_installation.vue | 58 ------ .../details/components/package_title.vue | 113 ----------- .../details/components/pypi_installation.vue | 71 ------- app/graphql/types/packages/package_type.rb | 2 + .../concerns/issue_available_features.rb | 3 +- app/models/issue.rb | 4 + app/services/issues/clone_service.rb | 22 +- app/services/issues/move_service.rb | 22 +- .../paginated_tree_graphql_query.yml | 2 +- doc/api/environments.md | 14 +- doc/api/graphql/reference/index.md | 3 +- doc/development/import_project.md | 6 +- doc/raketasks/index.md | 11 + .../dependency_scanning/index.md | 1 + .../clusters/management_project_template.md | 2 +- lib/api/entities/environment_basic.rb | 2 +- lib/gitlab/regex.rb | 6 +- locale/gitlab.pot | 6 + .../projects/infrastructure_registry_spec.rb | 6 +- .../graphql/packages/package_details.json | 4 + .../schemas/public_api/v4/environment.json | 7 +- .../conan_installation_spec.js.snap | 36 ---- .../__snapshots__/dependency_row_spec.js.snap | 34 ---- .../maven_installation_spec.js.snap | 112 ----------- .../npm_installation_spec.js.snap | 36 ---- .../nuget_installation_spec.js.snap | 36 ---- .../__snapshots__/package_title_spec.js.snap | 168 ---------------- .../pypi_installation_spec.js.snap | 45 ----- .../components/additional_metadata_spec.js | 119 ----------- .../packages/details/components/app_spec.js | 96 ++------- .../components/composer_installation_spec.js | 133 ------------ .../components/conan_installation_spec.js | 72 ------- .../details/components/dependency_row_spec.js | 62 ------ .../components/installation_title_spec.js | 58 ------ .../components/installations_commands_spec.js | 61 ------ .../components/maven_installation_spec.js | 184 ----------------- .../components/npm_installation_spec.js | 123 ------------ .../components/nuget_installation_spec.js | 79 -------- .../details/components/package_title_spec.js | 189 ------------------ .../components/pypi_installation_spec.js | 72 ------- spec/lib/gitlab/regex_spec.rb | 15 +- spec/models/issue_spec.rb | 20 ++ spec/requests/api/environments_spec.rb | 7 +- spec/services/projects/update_service_spec.rb | 2 +- ...d_project_packages_list_shared_examples.rb | 6 + 55 files changed, 159 insertions(+), 2643 deletions(-) delete mode 100644 app/assets/javascripts/packages/details/components/additional_metadata.vue delete mode 100644 app/assets/javascripts/packages/details/components/composer_installation.vue delete mode 100644 app/assets/javascripts/packages/details/components/conan_installation.vue delete mode 100644 app/assets/javascripts/packages/details/components/dependency_row.vue delete mode 100644 app/assets/javascripts/packages/details/components/installation_commands.vue delete mode 100644 app/assets/javascripts/packages/details/components/installation_title.vue delete mode 100644 app/assets/javascripts/packages/details/components/maven_installation.vue delete mode 100644 app/assets/javascripts/packages/details/components/npm_installation.vue delete mode 100644 app/assets/javascripts/packages/details/components/nuget_installation.vue delete mode 100644 app/assets/javascripts/packages/details/components/package_title.vue delete mode 100644 app/assets/javascripts/packages/details/components/pypi_installation.vue delete mode 100644 spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap delete mode 100644 spec/frontend/packages/details/components/additional_metadata_spec.js delete mode 100644 spec/frontend/packages/details/components/composer_installation_spec.js delete mode 100644 spec/frontend/packages/details/components/conan_installation_spec.js delete mode 100644 spec/frontend/packages/details/components/dependency_row_spec.js delete mode 100644 spec/frontend/packages/details/components/installation_title_spec.js delete mode 100644 spec/frontend/packages/details/components/installations_commands_spec.js delete mode 100644 spec/frontend/packages/details/components/maven_installation_spec.js delete mode 100644 spec/frontend/packages/details/components/npm_installation_spec.js delete mode 100644 spec/frontend/packages/details/components/nuget_installation_spec.js delete mode 100644 spec/frontend/packages/details/components/package_title_spec.js delete mode 100644 spec/frontend/packages/details/components/pypi_installation_spec.js diff --git a/app/assets/javascripts/ide/components/preview/navigator.vue b/app/assets/javascripts/ide/components/preview/navigator.vue index 838c363a6a3..96f9a85c23f 100644 --- a/app/assets/javascripts/ide/components/preview/navigator.vue +++ b/app/assets/javascripts/ide/components/preview/navigator.vue @@ -117,7 +117,7 @@ export default { class="ide-navigator-btn d-flex align-items-center d-transparent border-0 bg-transparent" @click="refresh" > - +
-import { GlLink, GlSprintf } from '@gitlab/ui'; -import { s__ } from '~/locale'; -import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; -import { PackageType } from '../../shared/constants'; - -export default { - i18n: { - sourceText: s__('PackageRegistry|Source project located at %{link}'), - licenseText: s__('PackageRegistry|License information located at %{link}'), - recipeText: s__('PackageRegistry|Recipe: %{recipe}'), - appGroup: s__('PackageRegistry|App group: %{group}'), - appName: s__('PackageRegistry|App name: %{name}'), - }, - components: { - DetailsRow, - GlLink, - GlSprintf, - }, - props: { - packageEntity: { - type: Object, - required: true, - }, - }, - computed: { - showMetadata() { - const visibilityConditions = { - [PackageType.NUGET]: this.packageEntity.nuget_metadatum, - [PackageType.CONAN]: this.packageEntity.conan_metadatum, - [PackageType.MAVEN]: this.packageEntity.maven_metadatum, - }; - return visibilityConditions[this.packageEntity.package_type]; - }, - }, -}; - - - diff --git a/app/assets/javascripts/packages/details/components/app.vue b/app/assets/javascripts/packages/details/components/app.vue index 59da32e6666..462252ce944 100644 --- a/app/assets/javascripts/packages/details/components/app.vue +++ b/app/assets/javascripts/packages/details/components/app.vue @@ -1,6 +1,5 @@ - - diff --git a/app/assets/javascripts/packages/details/components/conan_installation.vue b/app/assets/javascripts/packages/details/components/conan_installation.vue deleted file mode 100644 index 1d855f6cf3e..00000000000 --- a/app/assets/javascripts/packages/details/components/conan_installation.vue +++ /dev/null @@ -1,59 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/dependency_row.vue b/app/assets/javascripts/packages/details/components/dependency_row.vue deleted file mode 100644 index 1a2202b23c8..00000000000 --- a/app/assets/javascripts/packages/details/components/dependency_row.vue +++ /dev/null @@ -1,35 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/installation_commands.vue b/app/assets/javascripts/packages/details/components/installation_commands.vue deleted file mode 100644 index ed55d7fe782..00000000000 --- a/app/assets/javascripts/packages/details/components/installation_commands.vue +++ /dev/null @@ -1,55 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/installation_title.vue b/app/assets/javascripts/packages/details/components/installation_title.vue deleted file mode 100644 index 43133bf7825..00000000000 --- a/app/assets/javascripts/packages/details/components/installation_title.vue +++ /dev/null @@ -1,38 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/maven_installation.vue b/app/assets/javascripts/packages/details/components/maven_installation.vue deleted file mode 100644 index 6974de99344..00000000000 --- a/app/assets/javascripts/packages/details/components/maven_installation.vue +++ /dev/null @@ -1,153 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/npm_installation.vue b/app/assets/javascripts/packages/details/components/npm_installation.vue deleted file mode 100644 index 6b0fcf5e4fe..00000000000 --- a/app/assets/javascripts/packages/details/components/npm_installation.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/nuget_installation.vue b/app/assets/javascripts/packages/details/components/nuget_installation.vue deleted file mode 100644 index d5e64722f24..00000000000 --- a/app/assets/javascripts/packages/details/components/nuget_installation.vue +++ /dev/null @@ -1,58 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/package_title.vue b/app/assets/javascripts/packages/details/components/package_title.vue deleted file mode 100644 index d02a7b3ec27..00000000000 --- a/app/assets/javascripts/packages/details/components/package_title.vue +++ /dev/null @@ -1,113 +0,0 @@ - - - diff --git a/app/assets/javascripts/packages/details/components/pypi_installation.vue b/app/assets/javascripts/packages/details/components/pypi_installation.vue deleted file mode 100644 index fe4709d5feb..00000000000 --- a/app/assets/javascripts/packages/details/components/pypi_installation.vue +++ /dev/null @@ -1,71 +0,0 @@ - - - diff --git a/app/graphql/types/packages/package_type.rb b/app/graphql/types/packages/package_type.rb index f3fa79cc08c..45207581348 100644 --- a/app/graphql/types/packages/package_type.rb +++ b/app/graphql/types/packages/package_type.rb @@ -6,6 +6,8 @@ module Types graphql_name 'Package' description 'Represents a package in the Package Registry. Note that this type is in beta and susceptible to changes' + connection_type_class(Types::CountableConnectionType) + authorize :read_package field :id, ::Types::GlobalIDType[::Packages::Package], null: false, diff --git a/app/models/concerns/issue_available_features.rb b/app/models/concerns/issue_available_features.rb index 933e8b5f687..209456f8b67 100644 --- a/app/models/concerns/issue_available_features.rb +++ b/app/models/concerns/issue_available_features.rb @@ -12,7 +12,8 @@ module IssueAvailableFeatures { assignee: %w(issue incident), confidentiality: %w(issue incident), - time_tracking: %w(issue incident) + time_tracking: %w(issue incident), + move_and_clone: %w(issue incident) }.with_indifferent_access end end diff --git a/app/models/issue.rb b/app/models/issue.rb index edf5dd760c3..64254d3923c 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -542,6 +542,10 @@ class Issue < ApplicationRecord issue_type_supports?(:time_tracking) end + def supports_move_and_clone? + issue_type_supports?(:move_and_clone) + end + def email_participants_emails issue_email_participants.pluck(:email) end diff --git a/app/services/issues/clone_service.rb b/app/services/issues/clone_service.rb index cb42334fe32..c675f957cd7 100644 --- a/app/services/issues/clone_service.rb +++ b/app/services/issues/clone_service.rb @@ -8,13 +8,7 @@ module Issues @target_project = target_project @with_notes = with_notes - unless issue.can_clone?(current_user, target_project) - raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!') - end - - if target_project.pending_delete? - raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.') - end + verify_can_clone_issue!(issue, target_project) super(issue, target_project) @@ -30,6 +24,20 @@ module Issues attr_reader :target_project attr_reader :with_notes + def verify_can_clone_issue!(issue, target_project) + unless issue.supports_move_and_clone? + raise CloneError, s_('CloneIssue|Cannot clone issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type } + end + + unless issue.can_clone?(current_user, target_project) + raise CloneError, s_('CloneIssue|Cannot clone issue due to insufficient permissions!') + end + + if target_project.pending_delete? + raise CloneError, s_('CloneIssue|Cannot clone issue to target project as it is pending deletion.') + end + end + def update_new_entity # we don't call `super` because we want to be able to decide whether or not to copy all comments over. update_new_entity_description diff --git a/app/services/issues/move_service.rb b/app/services/issues/move_service.rb index ff78221c941..4418b4eb2bf 100644 --- a/app/services/issues/move_service.rb +++ b/app/services/issues/move_service.rb @@ -7,13 +7,7 @@ module Issues def execute(issue, target_project) @target_project = target_project - unless issue.can_move?(current_user, @target_project) - raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions!') - end - - if @project == @target_project - raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from!') - end + verify_can_move_issue!(issue, target_project) super @@ -32,6 +26,20 @@ module Issues attr_reader :target_project + def verify_can_move_issue!(issue, target_project) + unless issue.supports_move_and_clone? + raise MoveError, s_('MoveIssue|Cannot move issues of \'%{issue_type}\' type.') % { issue_type: issue.issue_type } + end + + unless issue.can_move?(current_user, @target_project) + raise MoveError, s_('MoveIssue|Cannot move issue due to insufficient permissions!') + end + + if @project == @target_project + raise MoveError, s_('MoveIssue|Cannot move issue to project it originates from!') + end + end + def update_service_desk_sent_notifications return unless original_entity.from_service_desk? diff --git a/config/feature_flags/development/paginated_tree_graphql_query.yml b/config/feature_flags/development/paginated_tree_graphql_query.yml index 13096412f25..d56d8fc336c 100644 --- a/config/feature_flags/development/paginated_tree_graphql_query.yml +++ b/config/feature_flags/development/paginated_tree_graphql_query.yml @@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/337214 milestone: '14.2' type: development group: group::source code -default_enabled: false +default_enabled: true diff --git a/doc/api/environments.md b/doc/api/environments.md index 5187ac7c1b2..8188e0e7b85 100644 --- a/doc/api/environments.md +++ b/doc/api/environments.md @@ -64,6 +64,8 @@ Example of response "slug": "review-fix-foo-dfjre3", "external_url": "https://review-fix-foo-dfjre3.gitlab.example.com", "state": "available", + "created_at": "2019-05-25T18:55:13.252Z", + "updated_at": "2019-05-27T18:55:13.252Z", "last_deployment": { "id": 100, "iid": 34, @@ -176,7 +178,9 @@ Example response: "name": "deploy", "slug": "deploy", "external_url": "https://deploy.gitlab.example.com", - "state": "available" + "state": "available", + "created_at": "2019-05-25T18:55:13.252Z", + "updated_at": "2019-05-27T18:55:13.252Z" } ``` @@ -210,7 +214,9 @@ Example response: "name": "staging", "slug": "staging", "external_url": "https://staging.gitlab.example.com", - "state": "available" + "state": "available", + "created_at": "2019-05-25T18:55:13.252Z", + "updated_at": "2019-05-27T18:55:13.252Z" } ``` @@ -302,6 +308,8 @@ Example response: "name": "deploy", "slug": "deploy", "external_url": "https://deploy.gitlab.example.com", - "state": "stopped" + "state": "stopped", + "created_at": "2019-05-25T18:55:13.252Z", + "updated_at": "2019-05-27T18:55:13.252Z" } ``` diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 6ffc4de5eff..d2736260e0c 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -6510,6 +6510,7 @@ The connection type for [`Package`](#package). | Name | Type | Description | | ---- | ---- | ----------- | +| `count` | [`Int!`](#int) | Total count of collection. | | `edges` | [`[PackageEdge]`](#packageedge) | A list of edges. | | `nodes` | [`[Package]`](#package) | A list of nodes. | | `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | @@ -13534,7 +13535,7 @@ Returns [`[String!]`](#string). ##### `Repository.paginatedTree` -Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is disabled by default, because the feature is experimental and is subject to change without notice. +Paginated tree of the repository. Available only when feature flag `paginated_tree_graphql_query` is enabled. This flag is enabled by default. Returns [`TreeConnection`](#treeconnection). diff --git a/doc/development/import_project.md b/doc/development/import_project.md index b5b8d7129eb..9872aa239dc 100644 --- a/doc/development/import_project.md +++ b/doc/development/import_project.md @@ -111,9 +111,9 @@ public folder (for example `/tmp/`) fixes the issue. ##### `Name can contain only letters, digits, emojis ...` ```plaintext -Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, -digit, emoji or '_'. and Path can contain only letters, digits, '_', '-' and '.'. Cannot start -with '-', end in '.git' or end in '.atom' +Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, +digit, emoji, or '_', and Path can contain only letters, digits, '_', '-', or '.'. It cannot start +with '-', end in '.git', or end in '.atom'. ``` The project name specified in `project_path` is not valid for one of the specified reasons. diff --git a/doc/raketasks/index.md b/doc/raketasks/index.md index df71b8791f8..65a5a06e26a 100644 --- a/doc/raketasks/index.md +++ b/doc/raketasks/index.md @@ -51,3 +51,14 @@ The following Rake tasks are available for use with GitLab: | [User management](user_management.md) | Perform user management tasks. | | [Webhooks administration](web_hooks.md) | Maintain project webhooks. | | [X.509 signatures](x509_signatures.md) | Update X.509 commit signatures, which can be useful if the certificate store changed. | + +To list all available Rake tasks: + +```shell +# Omnibus GitLab +sudo gitlab-rake -vT + +# Installations from source +cd /home/git/gitlab +sudo -u git -H bundle exec rake -vT RAILS_ENV=production +``` diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md index d903ce58982..6754e02f3d3 100644 --- a/doc/user/application_security/dependency_scanning/index.md +++ b/doc/user/application_security/dependency_scanning/index.md @@ -82,6 +82,7 @@ table.supported-languages tr td:last-child { } table.supported-languages ul { + font-size: 1em; list-style-type: none; padding-left: 0px; margin-bottom: 0px; diff --git a/doc/user/clusters/management_project_template.md b/doc/user/clusters/management_project_template.md index 6fd75f5c2ce..305f66c5ec5 100644 --- a/doc/user/clusters/management_project_template.md +++ b/doc/user/clusters/management_project_template.md @@ -12,7 +12,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w With a [cluster management project](management_project.md) you can manage your cluster's deployment and applications through a repository in GitLab. -The Custer Management project template provides you a baseline to get +The Cluster Management project template provides you a baseline to get started and flexibility to customize your project to your cluster's needs. For instance, you can: diff --git a/lib/api/entities/environment_basic.rb b/lib/api/entities/environment_basic.rb index 061d4739874..d9894eac147 100644 --- a/lib/api/entities/environment_basic.rb +++ b/lib/api/entities/environment_basic.rb @@ -3,7 +3,7 @@ module API module Entities class EnvironmentBasic < Grape::Entity - expose :id, :name, :slug, :external_url + expose :id, :name, :slug, :external_url, :created_at, :updated_at end end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index a88ef5fe73e..6331aced604 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -220,12 +220,12 @@ module Gitlab # The character range \p{Alnum} overlaps with \u{00A9}-\u{1f9ff} # hence the Ruby warning. # https://gitlab.com/gitlab-org/gitlab/merge_requests/23165#not-easy-fixable - @project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{00A9}-\u{1f9ff}_\. ]*\z/.freeze + @project_name_regex ||= /\A[\p{Alnum}\u{00A9}-\u{1f9ff}_][\p{Alnum}\p{Pd}\u{002B}\u{00A9}-\u{1f9ff}_\. ]*\z/.freeze end def project_name_regex_message - "can contain only letters, digits, emojis, '_', '.', dash, space. " \ - "It must start with letter, digit, emoji or '_'." + "can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. " \ + "It must start with a letter, digit, emoji, or '_'." end def group_name_regex diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 2d92e242378..a355063d5ea 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -7106,6 +7106,9 @@ msgstr "" msgid "CloneIssue|Cannot clone issue to target project as it is pending deletion." msgstr "" +msgid "CloneIssue|Cannot clone issues of '%{issue_type}' type." +msgstr "" + msgid "Cloned this issue to %{path_to_project}." msgstr "" @@ -22168,6 +22171,9 @@ msgstr "" msgid "MoveIssue|Cannot move issue to project it originates from!" msgstr "" +msgid "MoveIssue|Cannot move issues of '%{issue_type}' type." +msgstr "" + msgid "Moved issue to %{label} column in the board." msgstr "" diff --git a/spec/features/projects/infrastructure_registry_spec.rb b/spec/features/projects/infrastructure_registry_spec.rb index 16dd96e6c02..ee35e02b5e8 100644 --- a/spec/features/projects/infrastructure_registry_spec.rb +++ b/spec/features/projects/infrastructure_registry_spec.rb @@ -45,10 +45,8 @@ RSpec.describe 'Infrastructure Registry' do expect(page).to have_css('.packages-app h1[data-testid="title"]', text: terraform_module.name) - page.within(%Q([name="#{terraform_module.name}"])) do - expect(page).to have_content('Provision instructions') - expect(page).to have_content('Registry setup') - end + expect(page).to have_content('Provision instructions') + expect(page).to have_content('Registry setup') end end diff --git a/spec/fixtures/api/schemas/graphql/packages/package_details.json b/spec/fixtures/api/schemas/graphql/packages/package_details.json index 9e8bf7c52d0..9f483d1daf4 100644 --- a/spec/fixtures/api/schemas/graphql/packages/package_details.json +++ b/spec/fixtures/api/schemas/graphql/packages/package_details.json @@ -50,6 +50,7 @@ "type": "object", "additionalProperties": false, "properties": { + "count": { "type": "integer" }, "pageInfo": { "type": "object" }, "edges": { "type": "array" }, "nodes": { "type": "array" } @@ -72,6 +73,7 @@ "type": "object", "additionalProperties": false, "properties": { + "count": { "type": "integer" }, "pageInfo": { "type": "object" }, "edges": { "type": "array" }, "nodes": { "type": "array" } @@ -91,6 +93,7 @@ "type": "object", "additionalProperties": false, "properties": { + "count": { "type": "integer" }, "pageInfo": { "type": "object" }, "edges": { "type": "array" }, "nodes": { "type": "array" } @@ -106,6 +109,7 @@ "properties": { "pageInfo": { "type": "object" }, "edges": { "type": "array" }, + "count": { "type": "integer" }, "nodes": { "type": "array", "items": { diff --git a/spec/fixtures/api/schemas/public_api/v4/environment.json b/spec/fixtures/api/schemas/public_api/v4/environment.json index b90bfe8de55..30104adaf5c 100644 --- a/spec/fixtures/api/schemas/public_api/v4/environment.json +++ b/spec/fixtures/api/schemas/public_api/v4/environment.json @@ -5,7 +5,9 @@ "name", "slug", "external_url", - "last_deployment" + "state", + "created_at", + "updated_at" ], "properties": { "id": { "type": "integer" }, @@ -19,6 +21,9 @@ ] }, "state": { "type": "string" }, + "created_at": { "type": "string", "format": "date-time" }, + "updated_at": { "type": "string", "format": "date-time" }, + "project": { "$ref": "project.json" }, "enable_advanced_logs_querying": { "type": "boolean" }, "logs_api_path": { "type": "string" }, "gitlab_managed_apps_logs_path": { "type": "string" } diff --git a/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap deleted file mode 100644 index a3423e3f4d7..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/conan_installation_spec.js.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ConanInstallation renders all the messages 1`] = ` -
- - - - -

- Registry setup -

- - - - -
-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap deleted file mode 100644 index 39469bf4fd0..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/dependency_row_spec.js.snap +++ /dev/null @@ -1,34 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DependencyRow renders full dependency 1`] = ` -
-
- - Test.Dependency - - - - (.NETStandard2.0) - -
- -
- - 2.3.7 - -
-
-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap deleted file mode 100644 index 8a2793c0010..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/maven_installation_spec.js.snap +++ /dev/null @@ -1,112 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MavenInstallation groovy renders all the messages 1`] = ` -
- - - - - -
-`; - -exports[`MavenInstallation kotlin renders all the messages 1`] = ` -
- - - - - -
-`; - -exports[`MavenInstallation maven renders all the messages 1`] = ` -
- - -

- -

- - - - - -

- Registry setup -

- -

- -

- - - - -
-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap deleted file mode 100644 index 015c7b94dde..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/npm_installation_spec.js.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NpmInstallation renders all the messages 1`] = ` -
- - - - -

- Registry setup -

- - - - -
-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap deleted file mode 100644 index 04532743952..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/nuget_installation_spec.js.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`NugetInstallation renders all the messages 1`] = ` -
- - - - -

- Registry setup -

- - - - -
-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap deleted file mode 100644 index 318cea98b92..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/package_title_spec.js.snap +++ /dev/null @@ -1,168 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PackageTitle renders with tags 1`] = ` -
-
-
-
- - -
-

- Test package -

- -
- - - -
-
-
- -
-
- -
-
- -
-
- -
-
-
- - -
- -

-

-`; - -exports[`PackageTitle renders without tags 1`] = ` -
-
-
-
- - -
-

- Test package -

- -
- - - -
-
-
- -
-
- -
-
- -
-
-
- - -
- -

-

-`; diff --git a/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap b/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap deleted file mode 100644 index d5bb825d8d1..00000000000 --- a/spec/frontend/packages/details/components/__snapshots__/pypi_installation_spec.js.snap +++ /dev/null @@ -1,45 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`PypiInstallation renders all the messages 1`] = ` -
- - - - -

- Registry setup -

- -

- -

- - - - -
-`; diff --git a/spec/frontend/packages/details/components/additional_metadata_spec.js b/spec/frontend/packages/details/components/additional_metadata_spec.js deleted file mode 100644 index b339aa84348..00000000000 --- a/spec/frontend/packages/details/components/additional_metadata_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { GlLink, GlSprintf } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import component from '~/packages/details/components/additional_metadata.vue'; -import DetailsRow from '~/vue_shared/components/registry/details_row.vue'; - -import { mavenPackage, conanPackage, nugetPackage, npmPackage } from '../../mock_data'; - -describe('Package Additional Metadata', () => { - let wrapper; - const defaultProps = { - packageEntity: { ...mavenPackage }, - }; - - const mountComponent = (props) => { - wrapper = shallowMount(component, { - propsData: { ...defaultProps, ...props }, - stubs: { - DetailsRow, - GlSprintf, - }, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findTitle = () => wrapper.find('[data-testid="title"]'); - const findMainArea = () => wrapper.find('[data-testid="main"]'); - const findNugetSource = () => wrapper.find('[data-testid="nuget-source"]'); - const findNugetLicense = () => wrapper.find('[data-testid="nuget-license"]'); - const findConanRecipe = () => wrapper.find('[data-testid="conan-recipe"]'); - const findMavenApp = () => wrapper.find('[data-testid="maven-app"]'); - const findMavenGroup = () => wrapper.find('[data-testid="maven-group"]'); - const findElementLink = (container) => container.find(GlLink); - - it('has the correct title', () => { - mountComponent(); - - const title = findTitle(); - - expect(title.exists()).toBe(true); - expect(title.text()).toBe('Additional Metadata'); - }); - - describe.each` - packageEntity | visible | metadata - ${mavenPackage} | ${true} | ${'maven_metadatum'} - ${conanPackage} | ${true} | ${'conan_metadatum'} - ${nugetPackage} | ${true} | ${'nuget_metadatum'} - ${npmPackage} | ${false} | ${null} - `('Component visibility', ({ packageEntity, visible, metadata }) => { - it(`Is ${visible} that the component markup is visible when the package is ${packageEntity.package_type}`, () => { - mountComponent({ packageEntity }); - - expect(findTitle().exists()).toBe(visible); - expect(findMainArea().exists()).toBe(visible); - }); - - it(`The component is hidden if ${metadata} is missing`, () => { - mountComponent({ packageEntity: { ...packageEntity, [metadata]: null } }); - - expect(findTitle().exists()).toBe(false); - expect(findMainArea().exists()).toBe(false); - }); - }); - - describe('nuget metadata', () => { - beforeEach(() => { - mountComponent({ packageEntity: nugetPackage }); - }); - - it.each` - name | finderFunction | text | link | icon - ${'source'} | ${findNugetSource} | ${'Source project located at project-foo-url'} | ${'project_url'} | ${'project'} - ${'license'} | ${findNugetLicense} | ${'License information located at license-foo-url'} | ${'license_url'} | ${'license'} - `('$name element', ({ finderFunction, text, link, icon }) => { - const element = finderFunction(); - expect(element.exists()).toBe(true); - expect(element.text()).toBe(text); - expect(element.props('icon')).toBe(icon); - expect(findElementLink(element).attributes('href')).toBe(nugetPackage.nuget_metadatum[link]); - }); - }); - - describe('conan metadata', () => { - beforeEach(() => { - mountComponent({ packageEntity: conanPackage }); - }); - - it.each` - name | finderFunction | text | icon - ${'recipe'} | ${findConanRecipe} | ${'Recipe: conan-package/1.0.0@conan+conan-package/stable'} | ${'information-o'} - `('$name element', ({ finderFunction, text, icon }) => { - const element = finderFunction(); - expect(element.exists()).toBe(true); - expect(element.text()).toBe(text); - expect(element.props('icon')).toBe(icon); - }); - }); - - describe('maven metadata', () => { - beforeEach(() => { - mountComponent(); - }); - - it.each` - name | finderFunction | text | icon - ${'app'} | ${findMavenApp} | ${'App name: test-app'} | ${'information-o'} - ${'group'} | ${findMavenGroup} | ${'App group: com.test.app'} | ${'information-o'} - `('$name element', ({ finderFunction, text, icon }) => { - const element = finderFunction(); - expect(element.exists()).toBe(true); - expect(element.text()).toBe(text); - expect(element.props('icon')).toBe(icon); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/app_spec.js b/spec/frontend/packages/details/components/app_spec.js index 377e7e05f09..206c5fcf544 100644 --- a/spec/frontend/packages/details/components/app_spec.js +++ b/spec/frontend/packages/details/components/app_spec.js @@ -5,28 +5,19 @@ import Vuex from 'vuex'; import { useMockLocationHelper } from 'helpers/mock_window_location_helper'; import stubChildren from 'helpers/stub_children'; -import AdditionalMetadata from '~/packages/details/components/additional_metadata.vue'; import PackagesApp from '~/packages/details/components/app.vue'; -import DependencyRow from '~/packages/details/components/dependency_row.vue'; -import InstallationCommands from '~/packages/details/components/installation_commands.vue'; import PackageFiles from '~/packages/details/components/package_files.vue'; import PackageHistory from '~/packages/details/components/package_history.vue'; -import PackageTitle from '~/packages/details/components/package_title.vue'; import * as getters from '~/packages/details/store/getters'; import PackageListRow from '~/packages/shared/components/package_list_row.vue'; import PackagesListLoader from '~/packages/shared/components/packages_list_loader.vue'; import { TrackingActions } from '~/packages/shared/constants'; import * as SharedUtils from '~/packages/shared/utils'; +import TerraformTitle from '~/packages_and_registries/infrastructure_registry/components/details_title.vue'; +import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue'; import Tracking from '~/tracking'; -import { - composerPackage, - conanPackage, - mavenPackage, - mavenFiles, - npmPackage, - nugetPackage, -} from '../../mock_data'; +import { mavenPackage, mavenFiles, npmPackage } from '../../mock_data'; const localVue = createLocalVue(); localVue.use(Vuex); @@ -73,7 +64,7 @@ describe('PackagesApp', () => { store, stubs: { ...stubChildren(PackagesApp), - PackageTitle: false, + TerraformTitle: false, TitleArea: false, GlButton: false, GlModal: false, @@ -84,23 +75,18 @@ describe('PackagesApp', () => { }); } - const packageTitle = () => wrapper.find(PackageTitle); - const emptyState = () => wrapper.find(GlEmptyState); + const packageTitle = () => wrapper.findComponent(TerraformTitle); + const emptyState = () => wrapper.findComponent(GlEmptyState); const deleteButton = () => wrapper.find('.js-delete-button'); const findDeleteModal = () => wrapper.find({ ref: 'deleteModal' }); const findDeleteFileModal = () => wrapper.find({ ref: 'deleteFileModal' }); const versionsTab = () => wrapper.find('.js-versions-tab > a'); - const packagesLoader = () => wrapper.find(PackagesListLoader); - const packagesVersionRows = () => wrapper.findAll(PackageListRow); + const packagesLoader = () => wrapper.findComponent(PackagesListLoader); + const packagesVersionRows = () => wrapper.findAllComponents(PackageListRow); const noVersionsMessage = () => wrapper.find('[data-testid="no-versions-message"]'); - const dependenciesTab = () => wrapper.find('.js-dependencies-tab > a'); - const dependenciesCountBadge = () => wrapper.find('[data-testid="dependencies-badge"]'); - const noDependenciesMessage = () => wrapper.find('[data-testid="no-dependencies-message"]'); - const dependencyRows = () => wrapper.findAll(DependencyRow); - const findPackageHistory = () => wrapper.find(PackageHistory); - const findAdditionalMetadata = () => wrapper.find(AdditionalMetadata); - const findInstallationCommands = () => wrapper.find(InstallationCommands); - const findPackageFiles = () => wrapper.find(PackageFiles); + const findPackageHistory = () => wrapper.findComponent(PackageHistory); + const findTerraformInstallation = () => wrapper.findComponent(TerraformInstallation); + const findPackageFiles = () => wrapper.findComponent(PackageFiles); afterEach(() => { wrapper.destroy(); @@ -129,21 +115,10 @@ describe('PackagesApp', () => { expect(findPackageHistory().props('projectName')).toEqual(wrapper.vm.projectName); }); - it('additional metadata has the right props', () => { + it('terraform installation exists', () => { createComponent(); - expect(findAdditionalMetadata().exists()).toBe(true); - expect(findAdditionalMetadata().props('packageEntity')).toEqual(wrapper.vm.packageEntity); - }); - it('installation commands has the right props', () => { - createComponent(); - expect(findInstallationCommands().exists()).toBe(true); - expect(findInstallationCommands().props('packageEntity')).toEqual(wrapper.vm.packageEntity); - }); - - it('hides the files table if package type is COMPOSER', () => { - createComponent({ packageEntity: composerPackage }); - expect(findPackageFiles().exists()).toBe(false); + expect(findTerraformInstallation().exists()).toBe(true); }); describe('deleting packages', () => { @@ -198,45 +173,6 @@ describe('PackagesApp', () => { }); }); - describe('dependency links', () => { - it('does not show the dependency links for a non nuget package', () => { - createComponent(); - - expect(dependenciesTab().exists()).toBe(false); - }); - - it('shows the dependencies tab with 0 count when a nuget package with no dependencies', () => { - createComponent({ - packageEntity: { - ...nugetPackage, - dependency_links: [], - }, - }); - - return wrapper.vm.$nextTick(() => { - const dependenciesBadge = dependenciesCountBadge(); - - expect(dependenciesTab().exists()).toBe(true); - expect(dependenciesBadge.exists()).toBe(true); - expect(dependenciesBadge.text()).toBe('0'); - expect(noDependenciesMessage().exists()).toBe(true); - }); - }); - - it('renders the correct number of dependency rows for a nuget package', () => { - createComponent({ packageEntity: nugetPackage }); - - return wrapper.vm.$nextTick(() => { - const dependenciesBadge = dependenciesCountBadge(); - - expect(dependenciesTab().exists()).toBe(true); - expect(dependenciesBadge.exists()).toBe(true); - expect(dependenciesBadge.text()).toBe(nugetPackage.dependency_links.length.toString()); - expect(dependencyRows()).toHaveLength(nugetPackage.dependency_links.length); - }); - }); - }); - describe('tracking and delete', () => { describe('delete package', () => { const originalReferrer = document.referrer; @@ -305,9 +241,9 @@ describe('PackagesApp', () => { }); it('tracking category calls packageTypeToTrackCategory', () => { - createComponent({ packageEntity: conanPackage }); + createComponent({ packageEntity: npmPackage }); expect(wrapper.vm.tracking.category).toBe(category); - expect(utilSpy).toHaveBeenCalledWith('conan'); + expect(utilSpy).toHaveBeenCalledWith('npm'); }); it(`delete button on delete modal call event with ${TrackingActions.DELETE_PACKAGE}`, () => { @@ -371,7 +307,7 @@ describe('PackagesApp', () => { }); it(`file download link call event with ${TrackingActions.PULL_PACKAGE}`, () => { - createComponent({ packageEntity: conanPackage }); + createComponent({ packageEntity: npmPackage }); findPackageFiles().vm.$emit('download-file'); expect(eventSpy).toHaveBeenCalledWith( diff --git a/spec/frontend/packages/details/components/composer_installation_spec.js b/spec/frontend/packages/details/components/composer_installation_spec.js deleted file mode 100644 index 18d11c7dd57..00000000000 --- a/spec/frontend/packages/details/components/composer_installation_spec.js +++ /dev/null @@ -1,133 +0,0 @@ -import { GlSprintf, GlLink } from '@gitlab/ui'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { registryUrl as composerHelpPath } from 'jest/packages/details/mock_data'; -import { composerPackage as packageEntity } from 'jest/packages/mock_data'; -import ComposerInstallation from '~/packages/details/components/composer_installation.vue'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; - -import { TrackingActions } from '~/packages/details/constants'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ComposerInstallation', () => { - let wrapper; - let store; - - const composerRegistryIncludeStr = 'foo/registry'; - const composerPackageIncludeStr = 'foo/package'; - - const createStore = (groupExists = true) => { - store = new Vuex.Store({ - state: { packageEntity, composerHelpPath }, - getters: { - composerRegistryInclude: () => composerRegistryIncludeStr, - composerPackageInclude: () => composerPackageIncludeStr, - groupExists: () => groupExists, - }, - }); - }; - - const findRootNode = () => wrapper.find('[data-testid="root-node"]'); - const findRegistryInclude = () => wrapper.find('[data-testid="registry-include"]'); - const findPackageInclude = () => wrapper.find('[data-testid="package-include"]'); - const findHelpText = () => wrapper.find('[data-testid="help-text"]'); - const findHelpLink = () => wrapper.find(GlLink); - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent() { - wrapper = shallowMount(ComposerInstallation, { - localVue, - store, - stubs: { - GlSprintf, - }, - }); - } - - afterEach(() => { - wrapper.destroy(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - createStore(); - createComponent(); - - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'composer', - options: [{ value: 'composer', label: 'Show Composer commands' }], - }); - }); - }); - - describe('registry include command', () => { - beforeEach(() => { - createStore(); - createComponent(); - }); - - it('uses code_instructions', () => { - const registryIncludeCommand = findRegistryInclude(); - expect(registryIncludeCommand.exists()).toBe(true); - expect(registryIncludeCommand.props()).toMatchObject({ - instruction: composerRegistryIncludeStr, - copyText: 'Copy registry include', - trackingAction: TrackingActions.COPY_COMPOSER_REGISTRY_INCLUDE_COMMAND, - }); - }); - - it('has the correct title', () => { - expect(findRegistryInclude().props('label')).toBe('Add composer registry'); - }); - }); - - describe('package include command', () => { - beforeEach(() => { - createStore(); - createComponent(); - }); - - it('uses code_instructions', () => { - const registryIncludeCommand = findPackageInclude(); - expect(registryIncludeCommand.exists()).toBe(true); - expect(registryIncludeCommand.props()).toMatchObject({ - instruction: composerPackageIncludeStr, - copyText: 'Copy require package include', - trackingAction: TrackingActions.COPY_COMPOSER_PACKAGE_INCLUDE_COMMAND, - }); - }); - - it('has the correct title', () => { - expect(findPackageInclude().props('label')).toBe('Install package version'); - }); - - it('has the correct help text', () => { - expect(findHelpText().text()).toBe( - 'For more information on Composer packages in GitLab, see the documentation.', - ); - expect(findHelpLink().attributes()).toMatchObject({ - href: composerHelpPath, - target: '_blank', - }); - }); - }); - - describe('root node', () => { - it('is normally rendered', () => { - createStore(); - createComponent(); - - expect(findRootNode().exists()).toBe(true); - }); - - it('is not rendered when the group does not exist', () => { - createStore(false); - createComponent(); - - expect(findRootNode().exists()).toBe(false); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/conan_installation_spec.js b/spec/frontend/packages/details/components/conan_installation_spec.js deleted file mode 100644 index 78a7d265a21..00000000000 --- a/spec/frontend/packages/details/components/conan_installation_spec.js +++ /dev/null @@ -1,72 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import ConanInstallation from '~/packages/details/components/conan_installation.vue'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; -import { conanPackage as packageEntity } from '../../mock_data'; -import { registryUrl as conanPath } from '../mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('ConanInstallation', () => { - let wrapper; - - const conanInstallationCommandStr = 'foo/command'; - const conanSetupCommandStr = 'foo/setup'; - - const store = new Vuex.Store({ - state: { - packageEntity, - conanPath, - }, - getters: { - conanInstallationCommand: () => conanInstallationCommandStr, - conanSetupCommand: () => conanSetupCommandStr, - }, - }); - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent() { - wrapper = shallowMount(ConanInstallation, { - localVue, - store, - }); - } - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'conan', - options: [{ value: 'conan', label: 'Show Conan commands' }], - }); - }); - }); - - describe('installation commands', () => { - it('renders the correct command', () => { - expect(findCodeInstructions().at(0).props('instruction')).toBe(conanInstallationCommandStr); - }); - }); - - describe('setup commands', () => { - it('renders the correct command', () => { - expect(findCodeInstructions().at(1).props('instruction')).toBe(conanSetupCommandStr); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/dependency_row_spec.js b/spec/frontend/packages/details/components/dependency_row_spec.js deleted file mode 100644 index 7d3ee92908d..00000000000 --- a/spec/frontend/packages/details/components/dependency_row_spec.js +++ /dev/null @@ -1,62 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import DependencyRow from '~/packages/details/components/dependency_row.vue'; -import { dependencyLinks } from '../../mock_data'; - -describe('DependencyRow', () => { - let wrapper; - - const { withoutFramework, withoutVersion, fullLink } = dependencyLinks; - - function createComponent({ dependencyLink = fullLink } = {}) { - wrapper = shallowMount(DependencyRow, { - propsData: { - dependency: dependencyLink, - }, - }); - } - - const dependencyVersion = () => wrapper.find('[data-testid="version-pattern"]'); - const dependencyFramework = () => wrapper.find('[data-testid="target-framework"]'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('renders', () => { - it('full dependency', () => { - createComponent(); - - expect(wrapper.element).toMatchSnapshot(); - }); - }); - - describe('version', () => { - it('does not render any version information when not supplied', () => { - createComponent({ dependencyLink: withoutVersion }); - - expect(dependencyVersion().exists()).toBe(false); - }); - - it('does render version info when it exists', () => { - createComponent(); - - expect(dependencyVersion().exists()).toBe(true); - expect(dependencyVersion().text()).toBe(fullLink.version_pattern); - }); - }); - - describe('target framework', () => { - it('does not render any framework information when not supplied', () => { - createComponent({ dependencyLink: withoutFramework }); - - expect(dependencyFramework().exists()).toBe(false); - }); - - it('does render framework info when it exists', () => { - createComponent(); - - expect(dependencyFramework().exists()).toBe(true); - expect(dependencyFramework().text()).toBe(`(${fullLink.target_framework})`); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/installation_title_spec.js b/spec/frontend/packages/details/components/installation_title_spec.js deleted file mode 100644 index 14e990d3011..00000000000 --- a/spec/frontend/packages/details/components/installation_title_spec.js +++ /dev/null @@ -1,58 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; - -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import PersistedDropdownSelection from '~/vue_shared/components/registry/persisted_dropdown_selection.vue'; - -describe('InstallationTitle', () => { - let wrapper; - - const defaultProps = { packageType: 'foo', options: [{ value: 'foo', label: 'bar' }] }; - - const findPersistedDropdownSelection = () => wrapper.findComponent(PersistedDropdownSelection); - const findTitle = () => wrapper.find('h3'); - - function createComponent({ props = {} } = {}) { - wrapper = shallowMount(InstallationTitle, { - propsData: { - ...defaultProps, - ...props, - }, - }); - } - - afterEach(() => { - wrapper.destroy(); - }); - - it('has a title', () => { - createComponent(); - - expect(findTitle().exists()).toBe(true); - expect(findTitle().text()).toBe('Installation'); - }); - - describe('persisted dropdown selection', () => { - it('exists', () => { - createComponent(); - - expect(findPersistedDropdownSelection().exists()).toBe(true); - }); - - it('has the correct props', () => { - createComponent(); - - expect(findPersistedDropdownSelection().props()).toMatchObject({ - storageKey: 'package_foo_installation_instructions', - options: defaultProps.options, - }); - }); - - it('on change event emits a change event', () => { - createComponent(); - - findPersistedDropdownSelection().vm.$emit('change', 'baz'); - - expect(wrapper.emitted('change')).toEqual([['baz']]); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/installations_commands_spec.js b/spec/frontend/packages/details/components/installations_commands_spec.js deleted file mode 100644 index 164f9f69741..00000000000 --- a/spec/frontend/packages/details/components/installations_commands_spec.js +++ /dev/null @@ -1,61 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import ComposerInstallation from '~/packages/details/components/composer_installation.vue'; -import ConanInstallation from '~/packages/details/components/conan_installation.vue'; -import InstallationCommands from '~/packages/details/components/installation_commands.vue'; - -import MavenInstallation from '~/packages/details/components/maven_installation.vue'; -import NpmInstallation from '~/packages/details/components/npm_installation.vue'; -import NugetInstallation from '~/packages/details/components/nuget_installation.vue'; -import PypiInstallation from '~/packages/details/components/pypi_installation.vue'; -import TerraformInstallation from '~/packages_and_registries/infrastructure_registry/components/terraform_installation.vue'; - -import { - conanPackage, - mavenPackage, - npmPackage, - nugetPackage, - pypiPackage, - composerPackage, - terraformModule, -} from '../../mock_data'; - -describe('InstallationCommands', () => { - let wrapper; - - function createComponent(propsData) { - wrapper = shallowMount(InstallationCommands, { - propsData, - }); - } - - const npmInstallation = () => wrapper.find(NpmInstallation); - const mavenInstallation = () => wrapper.find(MavenInstallation); - const conanInstallation = () => wrapper.find(ConanInstallation); - const nugetInstallation = () => wrapper.find(NugetInstallation); - const pypiInstallation = () => wrapper.find(PypiInstallation); - const composerInstallation = () => wrapper.find(ComposerInstallation); - const terraformInstallation = () => wrapper.findComponent(TerraformInstallation); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('installation instructions', () => { - describe.each` - packageEntity | selector - ${conanPackage} | ${conanInstallation} - ${mavenPackage} | ${mavenInstallation} - ${npmPackage} | ${npmInstallation} - ${nugetPackage} | ${nugetInstallation} - ${pypiPackage} | ${pypiInstallation} - ${composerPackage} | ${composerInstallation} - ${terraformModule} | ${terraformInstallation} - `('renders', ({ packageEntity, selector }) => { - it(`${packageEntity.package_type} instructions exist`, () => { - createComponent({ packageEntity }); - - expect(selector()).toExist(); - }); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/maven_installation_spec.js b/spec/frontend/packages/details/components/maven_installation_spec.js deleted file mode 100644 index 4972fe70a3d..00000000000 --- a/spec/frontend/packages/details/components/maven_installation_spec.js +++ /dev/null @@ -1,184 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import Vuex from 'vuex'; -import { registryUrl as mavenPath } from 'jest/packages/details/mock_data'; -import { mavenPackage as packageEntity } from 'jest/packages/mock_data'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import MavenInstallation from '~/packages/details/components/maven_installation.vue'; -import { TrackingActions } from '~/packages/details/constants'; -import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('MavenInstallation', () => { - let wrapper; - - const xmlCodeBlock = 'foo/xml'; - const mavenCommandStr = 'foo/command'; - const mavenSetupXml = 'foo/setup'; - const gradleGroovyInstallCommandText = 'foo/gradle/groovy/install'; - const gradleGroovyAddSourceCommandText = 'foo/gradle/groovy/add/source'; - const gradleKotlinInstallCommandText = 'foo/gradle/kotlin/install'; - const gradleKotlinAddSourceCommandText = 'foo/gradle/kotlin/add/source'; - - const store = new Vuex.Store({ - state: { - packageEntity, - mavenPath, - }, - getters: { - mavenInstallationXml: () => xmlCodeBlock, - mavenInstallationCommand: () => mavenCommandStr, - mavenSetupXml: () => mavenSetupXml, - gradleGroovyInstalCommand: () => gradleGroovyInstallCommandText, - gradleGroovyAddSourceCommand: () => gradleGroovyAddSourceCommandText, - gradleKotlinInstalCommand: () => gradleKotlinInstallCommandText, - gradleKotlinAddSourceCommand: () => gradleKotlinAddSourceCommandText, - }, - }); - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent({ data = {} } = {}) { - wrapper = shallowMount(MavenInstallation, { - localVue, - store, - data() { - return data; - }, - }); - } - - afterEach(() => { - wrapper.destroy(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - createComponent(); - - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'maven', - options: [ - { value: 'maven', label: 'Maven XML' }, - { value: 'groovy', label: 'Gradle Groovy DSL' }, - { value: 'kotlin', label: 'Gradle Kotlin DSL' }, - ], - }); - }); - - it('on change event updates the instructions to show', async () => { - createComponent(); - - expect(findCodeInstructions().at(0).props('instruction')).toBe(xmlCodeBlock); - findInstallationTitle().vm.$emit('change', 'groovy'); - - await nextTick(); - - expect(findCodeInstructions().at(0).props('instruction')).toBe( - gradleGroovyInstallCommandText, - ); - }); - }); - - describe('maven', () => { - beforeEach(() => { - createComponent(); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('installation commands', () => { - it('renders the correct xml block', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: xmlCodeBlock, - multiline: true, - trackingAction: TrackingActions.COPY_MAVEN_XML, - }); - }); - - it('renders the correct maven command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: mavenCommandStr, - multiline: false, - trackingAction: TrackingActions.COPY_MAVEN_COMMAND, - }); - }); - }); - - describe('setup commands', () => { - it('renders the correct xml block', () => { - expect(findCodeInstructions().at(2).props()).toMatchObject({ - instruction: mavenSetupXml, - multiline: true, - trackingAction: TrackingActions.COPY_MAVEN_SETUP, - }); - }); - }); - }); - - describe('groovy', () => { - beforeEach(() => { - createComponent({ data: { instructionType: 'groovy' } }); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('installation commands', () => { - it('renders the gradle install command', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: gradleGroovyInstallCommandText, - multiline: false, - trackingAction: TrackingActions.COPY_GRADLE_INSTALL_COMMAND, - }); - }); - }); - - describe('setup commands', () => { - it('renders the correct gradle command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: gradleGroovyAddSourceCommandText, - multiline: true, - trackingAction: TrackingActions.COPY_GRADLE_ADD_TO_SOURCE_COMMAND, - }); - }); - }); - }); - - describe('kotlin', () => { - beforeEach(() => { - createComponent({ data: { instructionType: 'kotlin' } }); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('installation commands', () => { - it('renders the gradle install command', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: gradleKotlinInstallCommandText, - multiline: false, - trackingAction: TrackingActions.COPY_KOTLIN_INSTALL_COMMAND, - }); - }); - }); - - describe('setup commands', () => { - it('renders the correct gradle command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: gradleKotlinAddSourceCommandText, - multiline: true, - trackingAction: TrackingActions.COPY_KOTLIN_ADD_TO_SOURCE_COMMAND, - }); - }); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/npm_installation_spec.js b/spec/frontend/packages/details/components/npm_installation_spec.js deleted file mode 100644 index 1c49110bdf8..00000000000 --- a/spec/frontend/packages/details/components/npm_installation_spec.js +++ /dev/null @@ -1,123 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { nextTick } from 'vue'; -import Vuex from 'vuex'; -import { registryUrl as nugetPath } from 'jest/packages/details/mock_data'; -import { npmPackage as packageEntity } from 'jest/packages/mock_data'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import NpmInstallation from '~/packages/details/components/npm_installation.vue'; -import { TrackingActions } from '~/packages/details/constants'; -import { npmInstallationCommand, npmSetupCommand } from '~/packages/details/store/getters'; -import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('NpmInstallation', () => { - let wrapper; - - const npmInstallationCommandLabel = 'npm i @Test/package'; - const yarnInstallationCommandLabel = 'yarn add @Test/package'; - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent({ data = {} } = {}) { - const store = new Vuex.Store({ - state: { - packageEntity, - nugetPath, - }, - getters: { - npmInstallationCommand, - npmSetupCommand, - }, - }); - - wrapper = shallowMount(NpmInstallation, { - localVue, - store, - data() { - return data; - }, - }); - } - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'npm', - options: [ - { value: 'npm', label: 'Show NPM commands' }, - { value: 'yarn', label: 'Show Yarn commands' }, - ], - }); - }); - - it('on change event updates the instructions to show', async () => { - createComponent(); - - expect(findCodeInstructions().at(0).props('instruction')).toBe(npmInstallationCommandLabel); - findInstallationTitle().vm.$emit('change', 'yarn'); - - await nextTick(); - - expect(findCodeInstructions().at(0).props('instruction')).toBe(yarnInstallationCommandLabel); - }); - }); - - describe('npm', () => { - beforeEach(() => { - createComponent(); - }); - it('renders the correct installation command', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: npmInstallationCommandLabel, - multiline: false, - trackingAction: TrackingActions.COPY_NPM_INSTALL_COMMAND, - }); - }); - - it('renders the correct setup command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: 'echo @Test:registry=undefined/ >> .npmrc', - multiline: false, - trackingAction: TrackingActions.COPY_NPM_SETUP_COMMAND, - }); - }); - }); - - describe('yarn', () => { - beforeEach(() => { - createComponent({ data: { instructionType: 'yarn' } }); - }); - - it('renders the correct setup command', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: yarnInstallationCommandLabel, - multiline: false, - trackingAction: TrackingActions.COPY_YARN_INSTALL_COMMAND, - }); - }); - - it('renders the correct registry command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: 'echo \\"@Test:registry\\" \\"undefined/\\" >> .yarnrc', - multiline: false, - trackingAction: TrackingActions.COPY_YARN_SETUP_COMMAND, - }); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/nuget_installation_spec.js b/spec/frontend/packages/details/components/nuget_installation_spec.js deleted file mode 100644 index 8839a8f1108..00000000000 --- a/spec/frontend/packages/details/components/nuget_installation_spec.js +++ /dev/null @@ -1,79 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { registryUrl as nugetPath } from 'jest/packages/details/mock_data'; -import { nugetPackage as packageEntity } from 'jest/packages/mock_data'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import NugetInstallation from '~/packages/details/components/nuget_installation.vue'; -import { TrackingActions } from '~/packages/details/constants'; -import CodeInstructions from '~/vue_shared/components/registry/code_instruction.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('NugetInstallation', () => { - let wrapper; - - const nugetInstallationCommandStr = 'foo/command'; - const nugetSetupCommandStr = 'foo/setup'; - - const store = new Vuex.Store({ - state: { - packageEntity, - nugetPath, - }, - getters: { - nugetInstallationCommand: () => nugetInstallationCommandStr, - nugetSetupCommand: () => nugetSetupCommandStr, - }, - }); - - const findCodeInstructions = () => wrapper.findAll(CodeInstructions); - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent() { - wrapper = shallowMount(NugetInstallation, { - localVue, - store, - }); - } - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'nuget', - options: [{ value: 'nuget', label: 'Show Nuget commands' }], - }); - }); - }); - - describe('installation commands', () => { - it('renders the correct command', () => { - expect(findCodeInstructions().at(0).props()).toMatchObject({ - instruction: nugetInstallationCommandStr, - trackingAction: TrackingActions.COPY_NUGET_INSTALL_COMMAND, - }); - }); - }); - - describe('setup commands', () => { - it('renders the correct command', () => { - expect(findCodeInstructions().at(1).props()).toMatchObject({ - instruction: nugetSetupCommandStr, - trackingAction: TrackingActions.COPY_NUGET_SETUP_COMMAND, - }); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/package_title_spec.js b/spec/frontend/packages/details/components/package_title_spec.js deleted file mode 100644 index 512cec85b40..00000000000 --- a/spec/frontend/packages/details/components/package_title_spec.js +++ /dev/null @@ -1,189 +0,0 @@ -import { GlBreakpointInstance } from '@gitlab/ui/dist/utils'; -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import PackageTitle from '~/packages/details/components/package_title.vue'; -import PackageTags from '~/packages/shared/components/package_tags.vue'; -import TitleArea from '~/vue_shared/components/registry/title_area.vue'; -import { - conanPackage, - mavenFiles, - mavenPackage, - mockTags, - npmFiles, - npmPackage, - nugetPackage, -} from '../../mock_data'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('PackageTitle', () => { - let wrapper; - let store; - - function createComponent({ - packageEntity = mavenPackage, - packageFiles = mavenFiles, - icon = null, - } = {}) { - store = new Vuex.Store({ - state: { - packageEntity, - packageFiles, - }, - getters: { - packageTypeDisplay: ({ packageEntity: { package_type: type } }) => type, - packagePipeline: ({ packageEntity: { pipeline = null } }) => pipeline, - packageIcon: () => icon, - }, - }); - - wrapper = shallowMount(PackageTitle, { - localVue, - store, - stubs: { - TitleArea, - }, - }); - return wrapper.vm.$nextTick(); - } - - const findTitleArea = () => wrapper.find(TitleArea); - const packageType = () => wrapper.find('[data-testid="package-type"]'); - const packageSize = () => wrapper.find('[data-testid="package-size"]'); - const pipelineProject = () => wrapper.find('[data-testid="pipeline-project"]'); - const packageRef = () => wrapper.find('[data-testid="package-ref"]'); - const packageTags = () => wrapper.find(PackageTags); - const packageBadges = () => wrapper.findAll('[data-testid="tag-badge"]'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('renders', () => { - it('without tags', async () => { - await createComponent(); - - expect(wrapper.element).toMatchSnapshot(); - }); - - it('with tags', async () => { - await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } }); - - expect(wrapper.element).toMatchSnapshot(); - }); - - it('with tags on mobile', async () => { - jest.spyOn(GlBreakpointInstance, 'isDesktop').mockReturnValue(false); - await createComponent({ packageEntity: { ...mavenPackage, tags: mockTags } }); - await wrapper.vm.$nextTick(); - - expect(packageBadges()).toHaveLength(mockTags.length); - }); - }); - - describe('package title', () => { - it('is correctly bound', async () => { - await createComponent(); - - expect(findTitleArea().props('title')).toBe('Test package'); - }); - }); - - describe('package icon', () => { - const fakeSrc = 'a-fake-src'; - - it('binds an icon when provided one from vuex', async () => { - await createComponent({ icon: fakeSrc }); - - expect(findTitleArea().props('avatar')).toBe(fakeSrc); - }); - - it('do not binds an icon when not provided one', async () => { - await createComponent(); - - expect(findTitleArea().props('avatar')).toBe(null); - }); - }); - - describe.each` - packageEntity | text - ${conanPackage} | ${'conan'} - ${mavenPackage} | ${'maven'} - ${npmPackage} | ${'npm'} - ${nugetPackage} | ${'nuget'} - `(`package type`, ({ packageEntity, text }) => { - beforeEach(() => createComponent({ packageEntity })); - - it(`${packageEntity.package_type} should render from Vuex getters ${text}`, () => { - expect(packageType().props()).toEqual(expect.objectContaining({ text, icon: 'package' })); - }); - }); - - describe('calculates the package size', () => { - it('correctly calculates when there is only 1 file', async () => { - await createComponent({ packageEntity: npmPackage, packageFiles: npmFiles }); - - expect(packageSize().props()).toMatchObject({ text: '200 bytes', icon: 'disk' }); - }); - - it('correctly calulates when there are multiple files', async () => { - await createComponent(); - - expect(packageSize().props('text')).toBe('300 bytes'); - }); - }); - - describe('package tags', () => { - it('displays the package-tags component when the package has tags', async () => { - await createComponent({ - packageEntity: { - ...npmPackage, - tags: mockTags, - }, - }); - - expect(packageTags().exists()).toBe(true); - }); - - it('does not display the package-tags component when there are no tags', async () => { - await createComponent(); - - expect(packageTags().exists()).toBe(false); - }); - }); - - describe('package ref', () => { - it('does not display the ref if missing', async () => { - await createComponent(); - - expect(packageRef().exists()).toBe(false); - }); - - it('correctly shows the package ref if there is one', async () => { - await createComponent({ packageEntity: npmPackage }); - expect(packageRef().props()).toMatchObject({ - text: npmPackage.pipeline.ref, - icon: 'branch', - }); - }); - }); - - describe('pipeline project', () => { - it('does not display the project if missing', async () => { - await createComponent(); - - expect(pipelineProject().exists()).toBe(false); - }); - - it('correctly shows the pipeline project if there is one', async () => { - await createComponent({ packageEntity: npmPackage }); - - expect(pipelineProject().props()).toMatchObject({ - text: npmPackage.pipeline.project.name, - icon: 'review-list', - link: npmPackage.pipeline.project.web_url, - }); - }); - }); -}); diff --git a/spec/frontend/packages/details/components/pypi_installation_spec.js b/spec/frontend/packages/details/components/pypi_installation_spec.js deleted file mode 100644 index 2cec84282d9..00000000000 --- a/spec/frontend/packages/details/components/pypi_installation_spec.js +++ /dev/null @@ -1,72 +0,0 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { pypiPackage as packageEntity } from 'jest/packages/mock_data'; -import InstallationTitle from '~/packages/details/components/installation_title.vue'; -import PypiInstallation from '~/packages/details/components/pypi_installation.vue'; - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('PypiInstallation', () => { - let wrapper; - - const pipCommandStr = 'pip install'; - const pypiSetupStr = 'python setup'; - - const store = new Vuex.Store({ - state: { - packageEntity, - pypiHelpPath: 'foo', - }, - getters: { - pypiPipCommand: () => pipCommandStr, - pypiSetupCommand: () => pypiSetupStr, - }, - }); - - const pipCommand = () => wrapper.find('[data-testid="pip-command"]'); - const setupInstruction = () => wrapper.find('[data-testid="pypi-setup-content"]'); - - const findInstallationTitle = () => wrapper.findComponent(InstallationTitle); - - function createComponent() { - wrapper = shallowMount(PypiInstallation, { - localVue, - store, - }); - } - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('install command switch', () => { - it('has the installation title component', () => { - expect(findInstallationTitle().exists()).toBe(true); - expect(findInstallationTitle().props()).toMatchObject({ - packageType: 'pypi', - options: [{ value: 'pypi', label: 'Show PyPi commands' }], - }); - }); - }); - - it('renders all the messages', () => { - expect(wrapper.element).toMatchSnapshot(); - }); - - describe('installation commands', () => { - it('renders the correct pip command', () => { - expect(pipCommand().props('instruction')).toBe(pipCommandStr); - }); - }); - - describe('setup commands', () => { - it('renders the correct setup block', () => { - expect(setupInstruction().props('instruction')).toBe(pypiSetupStr); - }); - }); -}); diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index f1b4e50b1eb..b47f8520514 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -12,22 +12,29 @@ RSpec.describe Gitlab::Regex do it { is_expected.to match('Dash – is this') } end - shared_examples_for 'project/group name regex' do + shared_examples_for 'group name regex' do it_behaves_like 'project/group name chars regex' it { is_expected.not_to match('?gitlab') } it { is_expected.not_to match("Users's something") } end + shared_examples_for 'project name regex' do + it_behaves_like 'project/group name chars regex' + it { is_expected.to match("Gitlab++") } + it { is_expected.not_to match('?gitlab') } + it { is_expected.not_to match("Users's something") } + end + describe '.project_name_regex' do subject { described_class.project_name_regex } - it_behaves_like 'project/group name regex' + it_behaves_like 'project name regex' end describe '.group_name_regex' do subject { described_class.group_name_regex } - it_behaves_like 'project/group name regex' + it_behaves_like 'group name regex' it 'allows parenthesis' do is_expected.to match('Group One (Test)') @@ -51,7 +58,7 @@ RSpec.describe Gitlab::Regex do describe '.project_name_regex_message' do subject { described_class.project_name_regex_message } - it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.") } + it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'.") } end describe '.group_name_regex_message' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 1747972e8ae..d3d5a429a91 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -1505,6 +1505,26 @@ RSpec.describe Issue do end end + describe '#supports_move_and_clone?' do + let_it_be(:project) { create(:project) } + let_it_be_with_refind(:issue) { create(:incident, project: project) } + + where(:issue_type, :supports_move_and_clone) do + :issue | true + :incident | true + end + + with_them do + before do + issue.update!(issue_type: issue_type) + end + + it do + expect(issue.supports_move_and_clone?).to eq(supports_move_and_clone) + end + end + end + describe '#email_participants_emails' do let_it_be(:issue) { create(:issue) } diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index bc7bb7523c9..5fb24dc91a4 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -18,6 +18,7 @@ RSpec.describe API::Environments do get api("/projects/#{project.id}/environments", user) expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/environments') expect(response).to include_pagination_headers expect(json_response).to be_an Array expect(json_response.size).to eq(1) @@ -167,6 +168,7 @@ RSpec.describe API::Environments do post api("/projects/#{project.id}/environments", user), params: { name: "mepmep" } expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/environment') expect(json_response['name']).to eq('mepmep') expect(json_response['slug']).to eq('mepmep') expect(json_response['external']).to be nil @@ -212,6 +214,7 @@ RSpec.describe API::Environments do params: { name: 'Mepmep', external_url: url } expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/environment') expect(json_response['name']).to eq('Mepmep') expect(json_response['external_url']).to eq(url) end @@ -250,7 +253,7 @@ RSpec.describe API::Environments do expect(response).to have_gitlab_http_status(:forbidden) end - it 'returns a 200 for stopped environment' do + it 'returns a 204 for stopped environment' do environment.stop delete api("/projects/#{project.id}/environments/#{environment.id}", user) @@ -294,6 +297,7 @@ RSpec.describe API::Environments do it 'returns a 200' do expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/environment') end it 'actually stops the environment' do @@ -327,6 +331,7 @@ RSpec.describe API::Environments do expect(response).to have_gitlab_http_status(:ok) expect(response).to match_response_schema('public_api/v4/environment') + expect(json_response['last_deployment']).to be_present end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 3138dbc56db..4923ef169e8 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -374,7 +374,7 @@ RSpec.describe Projects::UpdateService do expect(result).to eq({ status: :error, - message: "Name can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'." + message: "Name can contain only letters, digits, emojis, '_', '.', '+', dashes, or spaces. It must start with a letter, digit, emoji, or '_'." }) end end diff --git a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb index af4c9286e7c..f1d8665468c 100644 --- a/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/graphql/packages/group_and_project_packages_list_shared_examples.rb @@ -17,9 +17,11 @@ RSpec.shared_examples 'group and project packages query' do let(:package_names) { graphql_data_at(resource_type, :packages, :nodes, :name) } let(:target_shas) { graphql_data_at(resource_type, :packages, :nodes, :metadata, :target_sha) } let(:packages) { graphql_data_at(resource_type, :packages, :nodes) } + let(:packages_count) { graphql_data_at(resource_type, :packages, :count) } let(:fields) do <<~QUERY + count nodes { #{all_graphql_fields_for('packages'.classify, excluded: ['project'])} metadata { #{query_graphql_fragment('ComposerMetadata')} } @@ -55,6 +57,10 @@ RSpec.shared_examples 'group and project packages query' do it 'deals with metadata' do expect(target_shas).to contain_exactly(composer_metadatum.target_sha) end + + it 'returns the count of the packages' do + expect(packages_count).to eq(4) + end end context 'when the user does not have access to the resource' do