summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE.md2
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md2
-rw-r--r--.gitlab/ci/qa.gitlab-ci.yml22
-rw-r--r--.gitlab/issue_templates/Bug.md13
-rw-r--r--.gitlab/issue_templates/Feature Flag Roll Out.md2
-rw-r--r--.gitlab/issue_templates/Problem_Validation.md4
-rw-r--r--.gitlab/issue_templates/Security Release.md8
-rw-r--r--.gitlab/issue_templates/Test plan.md4
-rw-r--r--app/assets/javascripts/registry/components/app.vue8
-rw-r--r--app/assets/javascripts/registry/components/collapsible_container.vue5
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue18
-rw-r--r--app/finders/clusters/kubernetes_namespace_finder.rb12
-rw-r--r--app/models/clusters/cluster.rb2
-rw-r--r--app/models/clusters/kubernetes_namespace.rb2
-rw-r--r--app/models/clusters/platforms/kubernetes.rb10
-rw-r--r--changelogs/unreleased/16790-render-xml-artifacts.yml (renamed from 16790-render-xml-artifacts.yml)0
-rw-r--r--changelogs/unreleased/sh-fix-any-approver-handling.yml5
-rw-r--r--doc/development/pipelines.md2
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb2
-rw-r--r--spec/finders/clusters/kubernetes_namespace_finder_spec.rb6
-rw-r--r--spec/frontend/registry/components/app_spec.js121
-rw-r--r--spec/frontend/registry/components/collapsible_container_spec.js89
-rw-r--r--spec/frontend/registry/components/table_registry_spec.js211
-rw-r--r--spec/frontend/registry/mock_data.js (renamed from spec/javascripts/registry/mock_data.js)0
-rw-r--r--spec/frontend/registry/stores/actions_spec.js189
-rw-r--r--spec/frontend/registry/stores/getters_spec.js (renamed from spec/frontend/registry/getters_spec.js)0
-rw-r--r--spec/frontend/registry/stores/mutations_spec.js (renamed from spec/javascripts/registry/stores/mutations_spec.js)0
-rw-r--r--spec/javascripts/registry/components/app_spec.js129
-rw-r--r--spec/javascripts/registry/components/collapsible_container_spec.js87
-rw-r--r--spec/javascripts/registry/components/table_registry_spec.js189
-rw-r--r--spec/javascripts/registry/stores/actions_spec.js132
-rw-r--r--spec/models/clusters/cluster_spec.rb2
-rw-r--r--spec/models/clusters/kubernetes_namespace_spec.rb14
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb2
34 files changed, 679 insertions, 615 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 2e88b7aa0a9..793a52e359d 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -1,3 +1,3 @@
We’re closing our issue tracker on GitHub so we can focus on the GitLab.com project and respond to issues more quickly.
-We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues). You can log into GitLab.com using your GitHub account.
+We encourage you to open an issue on the [GitLab.com issue tracker](https://gitlab.com/gitlab-org/gitlab/issues). You can log into GitLab.com using your GitHub account.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index c3b04026440..40984c451c4 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,3 +1,3 @@
Thank you for taking the time to contribute back to GitLab!
-Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account.
+Please open a merge request [on GitLab.com](https://gitlab.com/gitlab-org/gitlab/merge_requests), we look forward to reviewing your contribution! You can log into GitLab.com using your GitHub account.
diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml
index d07b9a1055c..6e6939824ec 100644
--- a/.gitlab/ci/qa.gitlab-ci.yml
+++ b/.gitlab/ci/qa.gitlab-ci.yml
@@ -24,17 +24,6 @@ package-and-qa-manual:
when: manual
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
-package-and-qa-manual:master:
- extends:
- - .package-and-qa-base
- - .only-code-qa-changes
- only:
- refs:
- - master@gitlab-org/gitlab-foss
- - master@gitlab-org/gitlab
- when: manual
- needs: ["build-qa-image", "gitlab:assets:compile"]
-
package-and-qa:
extends:
- .package-and-qa-base
@@ -44,3 +33,14 @@ package-and-qa:
- master
needs: ["build-qa-image", "gitlab:assets:compile pull-cache"]
allow_failure: true
+
+schedule:package-and-qa:
+ extends:
+ - .package-and-qa-base
+ - .only-code-qa-changes
+ only:
+ refs:
+ - schedules@gitlab-org/gitlab
+ - schedules@gitlab-org/gitlab-foss
+ needs: ["build-qa-image", "gitlab:assets:compile"]
+ allow_failure: true
diff --git a/.gitlab/issue_templates/Bug.md b/.gitlab/issue_templates/Bug.md
index 3adea22b33a..0d9990657e4 100644
--- a/.gitlab/issue_templates/Bug.md
+++ b/.gitlab/issue_templates/Bug.md
@@ -2,17 +2,10 @@
Please read this!
Before opening a new issue, make sure to search for keywords in the issues
-filtered by the "regression" or "bug" label.
+filtered by the "regression" or "bug" label:
-For the Community Edition issue tracker:
-
-- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=regression
-- https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name%5B%5D=bug
-
-For the Enterprise Edition issue tracker:
-
-- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=regression
-- https://gitlab.com/gitlab-org/gitlab-ee/issues?label_name%5B%5D=bug
+- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=regression
+- https://gitlab.com/gitlab-org/gitlab/issues?label_name%5B%5D=bug
and verify the issue you're about to submit isn't a duplicate.
--->
diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md
index 0cac769bd55..90c56417dbc 100644
--- a/.gitlab/issue_templates/Feature Flag Roll Out.md
+++ b/.gitlab/issue_templates/Feature Flag Roll Out.md
@@ -24,7 +24,7 @@ Remove the `:feature_name` feature flag ...
If applicable, any groups/projects that are happy to have this feature turned on early. Some organizations may wish to test big changes they are interested in with a small subset of users ahead of time for example.
-- `gitlab-org/gitlab-ce`/`gitlab-org/gitlab-ee` projects
+- `gitlab-org/gitlab` project
- `gitlab-org`/`gitlab-com` groups
- ...
diff --git a/.gitlab/issue_templates/Problem_Validation.md b/.gitlab/issue_templates/Problem_Validation.md
index d2bab21eb06..7440b41cf0b 100644
--- a/.gitlab/issue_templates/Problem_Validation.md
+++ b/.gitlab/issue_templates/Problem_Validation.md
@@ -26,7 +26,7 @@
## Confidence
-<!-- How do we know this is a problem? Please provide and link to any supporting information (e.g. data, customer verbatims) and use this basis to provide a numerical assessment on our confidence level in this problem's severity:
+<!-- How do we know this is a problem? Please provide and link to any supporting information (e.g. data, customer verbatims) and use this basis to provide a numerical assessment on our confidence level in this problem's severity:
100% = High confidence
80% = Medium confidence
@@ -34,7 +34,7 @@
## Effort
-<!-- How much effort do we think it will be to solve this problem? Please include all counterparts (Product, UX, Engineering, etc) in your assessment and quantify the number of person-months needed to dedicate to the effort.
+<!-- How much effort do we think it will be to solve this problem? Please include all counterparts (Product, UX, Engineering, etc) in your assessment and quantify the number of person-months needed to dedicate to the effort.
For example, if the solution will take a product manager, designer, and engineer two weeks of effort - you may quantify this as 1.5 (based on 0.5 months x 3 people). -->
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md
index 3e60274623e..e6e5d731d96 100644
--- a/.gitlab/issue_templates/Security Release.md
+++ b/.gitlab/issue_templates/Security Release.md
@@ -18,13 +18,7 @@ Set the title to: `Security Release: 12.2.X, 12.1.X, and 12.0.X`
## Security Issues:
-### CE
-
-* {https://gitlab.com/gitlab-org/gitlab-ce/issues link}
-
-### EE
-
-* {https://gitlab.com/gitlab-org/gitlab-ee/issues link}
+* {https://gitlab.com/gitlab-org/gitlab/issues link}
## Security Issues in dev.gitlab.org:
diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md
index f194adebc87..a202c0bf546 100644
--- a/.gitlab/issue_templates/Test plan.md
+++ b/.gitlab/issue_templates/Test plan.md
@@ -2,7 +2,7 @@
<!-- This issue outlines testing activities related to a particular issue or epic.
-[Here is an example test plan](https://gitlab.com/gitlab-org/gitlab-ce/issues/50353)
+[Here is an example test plan](https://gitlab.com/gitlab-org/gitlab-foss/issues/50353)
This and other comments should be removed as you write the plan -->
@@ -63,7 +63,7 @@ intersection of Components and Attributes.
Some features might be simple enough that they only involve one Component, while
more complex features could involve multiple or even all.
-Example (from https://gitlab.com/gitlab-org/gitlab-ce/issues/50353):
+Example (from https://gitlab.com/gitlab-org/gitlab-foss/issues/50353):
* Repository is
* Intuitive
* It's easy to select the desired file template
diff --git a/app/assets/javascripts/registry/components/app.vue b/app/assets/javascripts/registry/components/app.vue
index 346dc470a59..60aaef656a0 100644
--- a/app/assets/javascripts/registry/components/app.vue
+++ b/app/assets/javascripts/registry/components/app.vue
@@ -47,7 +47,7 @@ export default {
dockerConnectionErrorText() {
return sprintf(
s__(`ContainerRegistry|We are having trouble connecting to Docker, which could be due to an
- issue with your project name or path.
+ issue with your project name or path.
%{docLinkStart}More Information%{docLinkEnd}`),
{
docLinkStart: `<a href="${this.helpPagePath}#docker-connection-error" target="_blank">`,
@@ -58,8 +58,8 @@ export default {
},
introText() {
return sprintf(
- s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
- project can have its own space to store its Docker images.
+ s__(`ContainerRegistry|With the Docker Container Registry integrated into GitLab, every
+ project can have its own space to store its Docker images.
%{docLinkStart}More Information%{docLinkEnd}`),
{
docLinkStart: `<a href="${this.helpPagePath}" target="_blank">`,
@@ -109,7 +109,7 @@ export default {
:svg-path="containersErrorImage"
>
<template #description>
- <p v-html="dockerConnectionErrorText"></p>
+ <p class="js-character-error-text" v-html="dockerConnectionErrorText"></p>
</template>
</gl-empty-state>
diff --git a/app/assets/javascripts/registry/components/collapsible_container.vue b/app/assets/javascripts/registry/components/collapsible_container.vue
index bfb2305c48c..41bd9225c6e 100644
--- a/app/assets/javascripts/registry/components/collapsible_container.vue
+++ b/app/assets/javascripts/registry/components/collapsible_container.vue
@@ -49,7 +49,7 @@ export default {
}
},
handleDeleteRepository() {
- this.deleteItem(this.repo)
+ return this.deleteItem(this.repo)
.then(() => {
createFlash(__('This container registry has been scheduled for deletion.'), 'notice');
this.fetchRepos();
@@ -67,7 +67,8 @@ export default {
<div class="container-image">
<div class="container-image-head">
<gl-button class="js-toggle-repo btn-link align-baseline" @click="toggleRepo">
- <icon :name="iconName" /> {{ repo.name }}
+ <icon :name="iconName" />
+ {{ repo.name }}
</gl-button>
<clipboard-button
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index e9067bc2b56..ec0bad58b79 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -198,8 +198,9 @@ export default {
:title="s__('ContainerRegistry|Remove selected images')"
:aria-label="s__('ContainerRegistry|Remove selected images')"
@click="deleteMultipleItems()"
- ><icon name="remove"
- /></gl-button>
+ >
+ <icon name="remove" />
+ </gl-button>
</th>
</tr>
</thead>
@@ -223,9 +224,9 @@ export default {
/>
</td>
<td>
- <span v-gl-tooltip.bottom class="monospace" :title="item.revision">
- {{ item.shortRevision }}
- </span>
+ <span v-gl-tooltip.bottom class="monospace" :title="item.revision">{{
+ item.shortRevision
+ }}</span>
</td>
<td>
{{ formatSize(item.size) }}
@@ -236,9 +237,9 @@ export default {
</td>
<td>
- <span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">
- {{ timeFormated(item.createdAt) }}
- </span>
+ <span v-gl-tooltip.bottom :title="tooltipTitle(item.createdAt)">{{
+ timeFormated(item.createdAt)
+ }}</span>
</td>
<td class="content action-buttons">
@@ -262,6 +263,7 @@ export default {
v-if="shouldRenderPagination"
:change="onPageChange"
:page-info="repo.pagination"
+ class="js-registry-pagination"
/>
<gl-modal ref="deleteModal" :modal-id="modalId" ok-variant="danger">
diff --git a/app/finders/clusters/kubernetes_namespace_finder.rb b/app/finders/clusters/kubernetes_namespace_finder.rb
index e947796c1e7..82df96ed79e 100644
--- a/app/finders/clusters/kubernetes_namespace_finder.rb
+++ b/app/finders/clusters/kubernetes_namespace_finder.rb
@@ -2,12 +2,12 @@
module Clusters
class KubernetesNamespaceFinder
- attr_reader :cluster, :project, :environment_slug
+ attr_reader :cluster, :project, :environment_name
- def initialize(cluster, project:, environment_slug:, allow_blank_token: false)
+ def initialize(cluster, project:, environment_name:, allow_blank_token: false)
@cluster = cluster
@project = project
- @environment_slug = environment_slug
+ @environment_name = environment_name
@allow_blank_token = allow_blank_token
end
@@ -20,7 +20,11 @@ module Clusters
attr_reader :allow_blank_token
def find_namespace(with_environment:)
- relation = with_environment ? namespaces.with_environment_slug(environment_slug) : namespaces
+ relation = if with_environment
+ namespaces.with_environment_name(environment_name)
+ else
+ namespaces
+ end
relation.find_by_project_id(project.id)
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index 2df30e8ac36..49bed479c02 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -172,7 +172,7 @@ module Clusters
persisted_namespace = Clusters::KubernetesNamespaceFinder.new(
self,
project: project,
- environment_slug: environment.slug
+ environment_name: environment.name
).execute
persisted_namespace&.namespace || Gitlab::Kubernetes::DefaultNamespace.new(self, project: project).from_environment_slug(environment.slug)
diff --git a/app/models/clusters/kubernetes_namespace.rb b/app/models/clusters/kubernetes_namespace.rb
index 69a2b99fcb6..42332bdc193 100644
--- a/app/models/clusters/kubernetes_namespace.rb
+++ b/app/models/clusters/kubernetes_namespace.rb
@@ -27,7 +27,7 @@ module Clusters
algorithm: 'aes-256-cbc'
scope :has_service_account_token, -> { where.not(encrypted_service_account_token: nil) }
- scope :with_environment_slug, -> (slug) { joins(:environment).where(environments: { slug: slug }) }
+ scope :with_environment_name, -> (name) { joins(:environment).where(environments: { name: name }) }
def token_name
"#{namespace}-token"
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 89b50d8e8ff..aa2a7f3d7f1 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -105,19 +105,11 @@ module Clusters
private
- ##
- # Environment slug can be predicted given an environment
- # name, so even if the environment isn't persisted yet we
- # still know what to look for.
- def environment_slug(name)
- Gitlab::Slug::Environment.new(name).generate
- end
-
def find_persisted_namespace(project, environment_name:)
Clusters::KubernetesNamespaceFinder.new(
cluster,
project: project,
- environment_slug: environment_slug(environment_name)
+ environment_name: environment_name
).execute
end
diff --git a/16790-render-xml-artifacts.yml b/changelogs/unreleased/16790-render-xml-artifacts.yml
index d9cfcc04a68..d9cfcc04a68 100644
--- a/16790-render-xml-artifacts.yml
+++ b/changelogs/unreleased/16790-render-xml-artifacts.yml
diff --git a/changelogs/unreleased/sh-fix-any-approver-handling.yml b/changelogs/unreleased/sh-fix-any-approver-handling.yml
new file mode 100644
index 00000000000..4230dce3b74
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-any-approver-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug that caused a merge to show an error message
+merge_request: 17466
+author:
+type: fixed
diff --git a/doc/development/pipelines.md b/doc/development/pipelines.md
index 448fb0f9f5a..f01b83f4bd2 100644
--- a/doc/development/pipelines.md
+++ b/doc/development/pipelines.md
@@ -134,7 +134,7 @@ graph RL;
M[coverage];
N[pages];
O[static-analysis];
- P["package-and-qa-manual:master<br/>(master schedule only)"];
+ P["schedule:package-and-qa<br/>(master schedule only)"];
Q[package-and-qa];
R[package-and-qa-manual];
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
index f448d55f00a..9950e1dec55 100644
--- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -36,7 +36,7 @@ module Gitlab
Clusters::KubernetesNamespaceFinder.new(
deployment_cluster,
project: environment.project,
- environment_slug: environment.slug,
+ environment_name: environment.name,
allow_blank_token: true
).execute
end
diff --git a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
index 8beba0b99a4..7d9c4daa0fe 100644
--- a/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
+++ b/spec/finders/clusters/kubernetes_namespace_finder_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Clusters::KubernetesNamespaceFinder do
described_class.new(
cluster,
project: project,
- environment_slug: 'production',
+ environment_name: 'production',
allow_blank_token: allow_blank_token
)
end
@@ -22,8 +22,8 @@ RSpec.describe Clusters::KubernetesNamespaceFinder do
end
describe '#execute' do
- let(:production) { create(:environment, project: project, slug: 'production') }
- let(:staging) { create(:environment, project: project, slug: 'staging') }
+ let(:production) { create(:environment, project: project, name: 'production') }
+ let(:staging) { create(:environment, project: project, name: 'staging') }
let(:cluster) { create(:cluster, :group, :provided_by_user) }
let(:project) { create(:project) }
diff --git a/spec/frontend/registry/components/app_spec.js b/spec/frontend/registry/components/app_spec.js
new file mode 100644
index 00000000000..190af5c11cd
--- /dev/null
+++ b/spec/frontend/registry/components/app_spec.js
@@ -0,0 +1,121 @@
+import registry from '~/registry/components/app.vue';
+import { mount } from '@vue/test-utils';
+import { TEST_HOST } from '../../helpers/test_constants';
+import { reposServerResponse, parsedReposServerResponse } from '../mock_data';
+
+describe('Registry List', () => {
+ let wrapper;
+
+ const findCollapsibleContainer = w => w.findAll({ name: 'CollapsibeContainerRegisty' });
+ const findNoContainerImagesText = w => w.find('.js-no-container-images-text');
+ const findSpinner = w => w.find('.gl-spinner');
+ const findCharacterErrorText = w => w.find('.js-character-error-text');
+
+ const propsData = {
+ endpoint: `${TEST_HOST}/foo`,
+ helpPagePath: 'foo',
+ noContainersImage: 'foo',
+ containersErrorImage: 'foo',
+ repositoryUrl: 'foo',
+ };
+
+ const setMainEndpoint = jest.fn();
+ const fetchRepos = jest.fn();
+
+ const methods = {
+ setMainEndpoint,
+ fetchRepos,
+ };
+
+ beforeEach(() => {
+ wrapper = mount(registry, {
+ propsData,
+ computed: {
+ repos() {
+ return parsedReposServerResponse;
+ },
+ },
+ methods,
+ });
+ });
+
+ describe('with data', () => {
+ it('should render a list of CollapsibeContainerRegisty', () => {
+ const containers = findCollapsibleContainer(wrapper);
+ expect(wrapper.vm.repos.length).toEqual(reposServerResponse.length);
+ expect(containers.length).toEqual(reposServerResponse.length);
+ });
+ });
+
+ describe('without data', () => {
+ let localWrapper;
+ beforeEach(() => {
+ localWrapper = mount(registry, {
+ propsData,
+ computed: {
+ repos() {
+ return [];
+ },
+ },
+ methods,
+ });
+ });
+
+ it('should render empty message', () => {
+ const noContainerImagesText = findNoContainerImagesText(localWrapper);
+ expect(noContainerImagesText.text()).toEqual(
+ 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
+ );
+ });
+ });
+
+ describe('while loading data', () => {
+ let localWrapper;
+
+ beforeEach(() => {
+ localWrapper = mount(registry, {
+ propsData,
+ computed: {
+ repos() {
+ return [];
+ },
+ isLoading() {
+ return true;
+ },
+ },
+ methods,
+ });
+ });
+
+ it('should render a loading spinner', () => {
+ const spinner = findSpinner(localWrapper);
+ expect(spinner.exists()).toBe(true);
+ });
+ });
+
+ describe('invalid characters in path', () => {
+ let localWrapper;
+
+ beforeEach(() => {
+ localWrapper = mount(registry, {
+ propsData: {
+ ...propsData,
+ characterError: true,
+ },
+ computed: {
+ repos() {
+ return [];
+ },
+ },
+ methods,
+ });
+ });
+
+ it('should render invalid characters error message', () => {
+ const characterErrorText = findCharacterErrorText(localWrapper);
+ expect(characterErrorText.text()).toEqual(
+ 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More Information',
+ );
+ });
+ });
+});
diff --git a/spec/frontend/registry/components/collapsible_container_spec.js b/spec/frontend/registry/components/collapsible_container_spec.js
new file mode 100644
index 00000000000..0fe4338f1ba
--- /dev/null
+++ b/spec/frontend/registry/components/collapsible_container_spec.js
@@ -0,0 +1,89 @@
+import Vue from 'vue';
+import { mount } from '@vue/test-utils';
+import collapsibleComponent from '~/registry/components/collapsible_container.vue';
+import { repoPropsData } from '../mock_data';
+import createFlash from '~/flash';
+
+jest.mock('~/flash.js');
+
+describe('collapsible registry container', () => {
+ let wrapper;
+
+ const findDeleteBtn = w => w.find('.js-remove-repo');
+ const findContainerImageTags = w => w.find('.container-image-tags');
+ const findToggleRepos = w => w.findAll('.js-toggle-repo');
+
+ beforeEach(() => {
+ createFlash.mockClear();
+ // This is needed due to console.error called by vue to emit a warning that stop the tests
+ // see https://github.com/vuejs/vue-test-utils/issues/532
+ Vue.config.silent = true;
+ wrapper = mount(collapsibleComponent, {
+ propsData: {
+ repo: repoPropsData,
+ },
+ });
+ });
+
+ afterEach(() => {
+ Vue.config.silent = false;
+ });
+
+ describe('toggle', () => {
+ beforeEach(() => {
+ const fetchList = jest.fn();
+ wrapper.setMethods({ fetchList });
+ });
+
+ const expectIsClosed = () => {
+ const container = findContainerImageTags(wrapper);
+ expect(container.exists()).toBe(false);
+ expect(wrapper.vm.iconName).toEqual('angle-right');
+ };
+
+ it('should be closed by default', () => {
+ expectIsClosed();
+ });
+ it('should be open when user clicks on closed repo', () => {
+ const toggleRepos = findToggleRepos(wrapper);
+ toggleRepos.at(0).trigger('click');
+ const container = findContainerImageTags(wrapper);
+ expect(container.exists()).toBe(true);
+ expect(wrapper.vm.fetchList).toHaveBeenCalled();
+ });
+ it('should be closed when the user clicks on an opened repo', done => {
+ const toggleRepos = findToggleRepos(wrapper);
+ toggleRepos.at(0).trigger('click');
+ Vue.nextTick(() => {
+ toggleRepos.at(0).trigger('click');
+ Vue.nextTick(() => {
+ expectIsClosed();
+ done();
+ });
+ });
+ });
+ });
+
+ describe('delete repo', () => {
+ it('should be possible to delete a repo', () => {
+ const deleteBtn = findDeleteBtn(wrapper);
+ expect(deleteBtn.exists()).toBe(true);
+ });
+
+ it('should call deleteItem when confirming deletion', () => {
+ const deleteItem = jest.fn().mockResolvedValue();
+ const fetchRepos = jest.fn().mockResolvedValue();
+ wrapper.setMethods({ deleteItem, fetchRepos });
+ wrapper.vm.handleDeleteRepository();
+ expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(wrapper.vm.repo);
+ });
+
+ it('should show an error when there is API error', () => {
+ const deleteItem = jest.fn().mockRejectedValue('error');
+ wrapper.setMethods({ deleteItem });
+ return wrapper.vm.handleDeleteRepository().then(() => {
+ expect(createFlash).toHaveBeenCalled();
+ });
+ });
+ });
+});
diff --git a/spec/frontend/registry/components/table_registry_spec.js b/spec/frontend/registry/components/table_registry_spec.js
new file mode 100644
index 00000000000..a2eee4aada7
--- /dev/null
+++ b/spec/frontend/registry/components/table_registry_spec.js
@@ -0,0 +1,211 @@
+import Vue from 'vue';
+import tableRegistry from '~/registry/components/table_registry.vue';
+import { mount } from '@vue/test-utils';
+import { repoPropsData } from '../mock_data';
+
+const [firstImage, secondImage] = repoPropsData.list;
+
+describe('table registry', () => {
+ let wrapper;
+
+ const findSelectAllCheckbox = w => w.find('.js-select-all-checkbox > input');
+ const findSelectCheckboxes = w => w.findAll('.js-select-checkbox > input');
+ const findDeleteButton = w => w.find('.js-delete-registry');
+ const findDeleteButtonsRow = w => w.findAll('.js-delete-registry-row');
+ const findPagination = w => w.find('.js-registry-pagination');
+ const bulkDeletePath = 'path';
+
+ beforeEach(() => {
+ // This is needed due to console.error called by vue to emit a warning that stop the tests
+ // see https://github.com/vuejs/vue-test-utils/issues/532
+ Vue.config.silent = true;
+ wrapper = mount(tableRegistry, {
+ propsData: {
+ repo: repoPropsData,
+ },
+ });
+ });
+
+ afterEach(() => {
+ Vue.config.silent = false;
+ });
+
+ describe('rendering', () => {
+ it('should render a table with the registry list', () => {
+ expect(wrapper.findAll('.registry-image-row').length).toEqual(repoPropsData.list.length);
+ });
+
+ it('should render registry tag', () => {
+ const tds = wrapper.findAll('.registry-image-row td');
+ expect(tds.at(0).classes()).toContain('check');
+ expect(tds.at(1).html()).toContain(repoPropsData.list[0].tag);
+ expect(tds.at(2).html()).toContain(repoPropsData.list[0].shortRevision);
+ expect(tds.at(3).html()).toContain(repoPropsData.list[0].layers);
+ expect(tds.at(3).html()).toContain(repoPropsData.list[0].size);
+ expect(tds.at(4).html()).toContain(wrapper.vm.timeFormated(repoPropsData.list[0].createdAt));
+ });
+ });
+
+ describe('multi select', () => {
+ it('selecting a row should enable delete button', done => {
+ const deleteBtn = findDeleteButton(wrapper);
+ const checkboxes = findSelectCheckboxes(wrapper);
+
+ expect(deleteBtn.attributes('disabled')).toBe('disabled');
+
+ checkboxes.at(0).trigger('click');
+ Vue.nextTick(() => {
+ expect(deleteBtn.attributes('disabled')).toEqual(undefined);
+ done();
+ });
+ });
+
+ it('selecting all checkbox should select all rows and enable delete button', done => {
+ const selectAll = findSelectAllCheckbox(wrapper);
+ const checkboxes = findSelectCheckboxes(wrapper);
+ selectAll.trigger('click');
+
+ Vue.nextTick(() => {
+ const checked = checkboxes.filter(w => w.element.checked);
+ expect(checked.length).toBe(checkboxes.length);
+ done();
+ });
+ });
+
+ it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
+ const checkboxes = findSelectCheckboxes(wrapper);
+ const selectAll = findSelectAllCheckbox(wrapper);
+ selectAll.trigger('click');
+ selectAll.trigger('click');
+
+ Vue.nextTick(() => {
+ const checked = checkboxes.filter(w => !w.element.checked);
+ expect(checked.length).toBe(checkboxes.length);
+ done();
+ });
+ });
+
+ it('should delete multiple items when multiple items are selected', done => {
+ const multiDeleteItems = jest.fn().mockResolvedValue();
+ wrapper.setMethods({ multiDeleteItems });
+ const selectAll = findSelectAllCheckbox(wrapper);
+ selectAll.trigger('click');
+
+ Vue.nextTick(() => {
+ const deleteBtn = findDeleteButton(wrapper);
+ expect(wrapper.vm.itemsToBeDeleted).toEqual([0, 1]);
+ expect(deleteBtn.attributes('disabled')).toEqual(undefined);
+ wrapper.vm.handleMultipleDelete();
+
+ Vue.nextTick(() => {
+ expect(wrapper.vm.itemsToBeDeleted).toEqual([]);
+ expect(wrapper.vm.multiDeleteItems).toHaveBeenCalledWith({
+ path: bulkDeletePath,
+ items: [firstImage.tag, secondImage.tag],
+ });
+ done();
+ });
+ });
+ });
+
+ it('should show an error message if bulkDeletePath is not set', () => {
+ const showError = jest.fn();
+ wrapper.setMethods({ showError });
+ wrapper.setProps({
+ repo: {
+ ...repoPropsData,
+ tagsPath: null,
+ },
+ });
+ wrapper.vm.handleMultipleDelete();
+ expect(wrapper.vm.showError).toHaveBeenCalled();
+ });
+ });
+
+ describe('delete registry', () => {
+ beforeEach(() => {
+ wrapper.setData({ itemsToBeDeleted: [0] });
+ });
+
+ it('should be possible to delete a registry', () => {
+ const deleteBtn = findDeleteButton(wrapper);
+ const deleteBtns = findDeleteButtonsRow(wrapper);
+ expect(wrapper.vm.itemsToBeDeleted).toEqual([0]);
+ expect(deleteBtn).toBeDefined();
+ expect(deleteBtn.attributes('disable')).toBe(undefined);
+ expect(deleteBtns.is('button')).toBe(true);
+ });
+
+ it('should allow deletion row by row', () => {
+ const deleteBtns = findDeleteButtonsRow(wrapper);
+ const deleteSingleItem = jest.fn();
+ const deleteItem = jest.fn().mockResolvedValue();
+ wrapper.setMethods({ deleteSingleItem, deleteItem });
+ deleteBtns.at(0).trigger('click');
+ expect(wrapper.vm.deleteSingleItem).toHaveBeenCalledWith(0);
+ wrapper.vm.handleSingleDelete(1);
+ expect(wrapper.vm.deleteItem).toHaveBeenCalledWith(1);
+ });
+ });
+
+ describe('pagination', () => {
+ let localWrapper = null;
+ const repo = {
+ repoPropsData,
+ pagination: {
+ total: 20,
+ perPage: 2,
+ nextPage: 2,
+ },
+ };
+
+ beforeEach(() => {
+ localWrapper = mount(tableRegistry, {
+ propsData: {
+ repo,
+ },
+ });
+ });
+
+ it('should exist', () => {
+ const pagination = findPagination(localWrapper);
+ expect(pagination.exists()).toBe(true);
+ });
+ it('should be visible when pagination is needed', () => {
+ const pagination = findPagination(localWrapper);
+ expect(pagination.isVisible()).toBe(true);
+ localWrapper.setProps({
+ repo: {
+ pagination: {
+ total: 0,
+ perPage: 10,
+ },
+ },
+ });
+ expect(localWrapper.vm.shouldRenderPagination).toBe(false);
+ });
+ it('should have a change function that update the list when run', () => {
+ const fetchList = jest.fn().mockResolvedValue();
+ localWrapper.setMethods({ fetchList });
+ localWrapper.vm.onPageChange(1);
+ expect(localWrapper.vm.fetchList).toHaveBeenCalledWith({ repo, page: 1 });
+ });
+ });
+
+ describe('modal content', () => {
+ it('should show the singular title and image name when deleting a single image', () => {
+ wrapper.setData({ itemsToBeDeleted: [1] });
+ wrapper.vm.setModalDescription(0);
+ expect(wrapper.vm.modalTitle).toBe('Remove image');
+ expect(wrapper.vm.modalDescription).toContain(firstImage.tag);
+ });
+
+ it('should show the plural title and image count when deleting more than one image', () => {
+ wrapper.setData({ itemsToBeDeleted: [1, 2] });
+ wrapper.vm.setModalDescription();
+
+ expect(wrapper.vm.modalTitle).toBe('Remove images');
+ expect(wrapper.vm.modalDescription).toContain('<b>2</b> images');
+ });
+ });
+});
diff --git a/spec/javascripts/registry/mock_data.js b/spec/frontend/registry/mock_data.js
index 130ab298e89..130ab298e89 100644
--- a/spec/javascripts/registry/mock_data.js
+++ b/spec/frontend/registry/mock_data.js
diff --git a/spec/frontend/registry/stores/actions_spec.js b/spec/frontend/registry/stores/actions_spec.js
new file mode 100644
index 00000000000..bf335904d23
--- /dev/null
+++ b/spec/frontend/registry/stores/actions_spec.js
@@ -0,0 +1,189 @@
+import MockAdapter from 'axios-mock-adapter';
+import axios from '~/lib/utils/axios_utils';
+import * as actions from '~/registry/stores/actions';
+import * as types from '~/registry/stores/mutation_types';
+import { TEST_HOST } from '../../helpers/test_constants';
+import testAction from '../../helpers/vuex_action_helper';
+import createFlash from '~/flash';
+
+import {
+ reposServerResponse,
+ registryServerResponse,
+ parsedReposServerResponse,
+} from '../mock_data';
+
+jest.mock('~/flash.js');
+
+describe('Actions Registry Store', () => {
+ let mock;
+ let state;
+
+ beforeEach(() => {
+ mock = new MockAdapter(axios);
+ state = {
+ endpoint: `${TEST_HOST}/endpoint.json`,
+ };
+ });
+
+ afterEach(() => {
+ mock.restore();
+ });
+
+ describe('fetchRepos', () => {
+ beforeEach(() => {
+ mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {});
+ });
+
+ it('should set receveived repos', done => {
+ testAction(
+ actions.fetchRepos,
+ null,
+ state,
+ [
+ { type: types.TOGGLE_MAIN_LOADING },
+ { type: types.TOGGLE_MAIN_LOADING },
+ { type: types.SET_REPOS_LIST, payload: reposServerResponse },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ testAction(
+ actions.fetchRepos,
+ null,
+ {
+ endpoint: null,
+ },
+ [{ type: types.TOGGLE_MAIN_LOADING }, { type: types.TOGGLE_MAIN_LOADING }],
+ [],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('fetchList', () => {
+ let repo;
+ beforeEach(() => {
+ state.repos = parsedReposServerResponse;
+ [, repo] = state.repos;
+ mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {});
+ });
+
+ it('should set received list', done => {
+ testAction(
+ actions.fetchList,
+ { repo },
+ state,
+ [
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
+ {
+ type: types.SET_REGISTRY_LIST,
+ payload: {
+ repo,
+ resp: registryServerResponse,
+ headers: expect.anything(),
+ },
+ },
+ ],
+ [],
+ done,
+ );
+ });
+
+ it('should create flash on API error', done => {
+ const updatedRepo = {
+ ...repo,
+ tagsPath: null,
+ };
+ testAction(
+ actions.fetchList,
+ {
+ repo: updatedRepo,
+ },
+ state,
+ [
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo },
+ { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: updatedRepo },
+ ],
+ [],
+ () => {
+ expect(createFlash).toHaveBeenCalled();
+ done();
+ },
+ );
+ });
+ });
+
+ describe('setMainEndpoint', () => {
+ it('should commit set main endpoint', done => {
+ testAction(
+ actions.setMainEndpoint,
+ 'endpoint',
+ state,
+ [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('toggleLoading', () => {
+ it('should commit toggle main loading', done => {
+ testAction(
+ actions.toggleLoading,
+ null,
+ state,
+ [{ type: types.TOGGLE_MAIN_LOADING }],
+ [],
+ done,
+ );
+ });
+ });
+
+ describe('deleteItem and multiDeleteItems', () => {
+ let deleted;
+ const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`;
+
+ const expectDelete = done => {
+ expect(mock.history.delete.length).toBe(1);
+ expect(deleted).toBe(true);
+ done();
+ };
+
+ beforeEach(() => {
+ deleted = false;
+ mock.onDelete(destroyPath).replyOnce(() => {
+ deleted = true;
+ return [200];
+ });
+ });
+
+ it('deleteItem should perform DELETE request on destroyPath', done => {
+ testAction(
+ actions.deleteItem,
+ {
+ destroyPath,
+ },
+ state,
+ )
+ .then(() => {
+ expectDelete(done);
+ })
+ .catch(done.fail);
+ });
+
+ it('multiDeleteItems should perform DELETE request on path', done => {
+ testAction(actions.multiDeleteItems, { path: destroyPath, items: [1] }, state)
+ .then(() => {
+ expectDelete(done);
+ })
+ .catch(done.fail);
+ });
+ });
+});
diff --git a/spec/frontend/registry/getters_spec.js b/spec/frontend/registry/stores/getters_spec.js
index 839aa718997..839aa718997 100644
--- a/spec/frontend/registry/getters_spec.js
+++ b/spec/frontend/registry/stores/getters_spec.js
diff --git a/spec/javascripts/registry/stores/mutations_spec.js b/spec/frontend/registry/stores/mutations_spec.js
index e19fe7a27cf..e19fe7a27cf 100644
--- a/spec/javascripts/registry/stores/mutations_spec.js
+++ b/spec/frontend/registry/stores/mutations_spec.js
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
deleted file mode 100644
index 5ea3f85a247..00000000000
--- a/spec/javascripts/registry/components/app_spec.js
+++ /dev/null
@@ -1,129 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Vue from 'vue';
-import registry from '~/registry/components/app.vue';
-import mountComponent from 'spec/helpers/vue_mount_component_helper';
-import { TEST_HOST } from 'spec/test_constants';
-import { reposServerResponse } from '../mock_data';
-
-describe('Registry List', () => {
- const Component = Vue.extend(registry);
- const props = {
- endpoint: `${TEST_HOST}/foo`,
- helpPagePath: 'foo',
- noContainersImage: 'foo',
- containersErrorImage: 'foo',
- repositoryUrl: 'foo',
- };
- let vm;
- let mock;
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- vm.$destroy();
- });
-
- describe('with data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, reposServerResponse);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render a list of repos', done => {
- setTimeout(() => {
- expect(vm.$store.state.repos.length).toEqual(reposServerResponse.length);
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelectorAll('.container-image').length).toEqual(
- reposServerResponse.length,
- );
- done();
- });
- }, 0);
- });
-
- describe('delete repository', () => {
- it('should be possible to delete a repo', done => {
- setTimeout(() => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-head .js-remove-repo')).toBeDefined();
- done();
- });
- }, 0);
- });
- });
-
- describe('toggle repository', () => {
- it('should open the container', done => {
- setTimeout(() => {
- Vue.nextTick(() => {
- vm.$el.querySelector('.js-toggle-repo').click();
- Vue.nextTick(() => {
- expect(
- vm.$el.querySelector('.js-toggle-repo use').getAttribute('xlink:href'),
- ).toContain('angle-up');
- done();
- });
- });
- }, 0);
- });
- });
- });
-
- describe('without data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render empty message', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('.js-no-container-images-text').textContent).toEqual(
- 'With the Container Registry, every project can have its own space to store its Docker images. More Information',
- );
- done();
- }, 0);
- });
- });
-
- describe('while loading data', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, { ...props });
- });
-
- it('should render a loading spinner', done => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.gl-spinner')).not.toBe(null);
- done();
- });
- });
- });
-
- describe('invalid characters in path', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/foo`).replyOnce(200, []);
-
- vm = mountComponent(Component, {
- ...props,
- characterError: true,
- });
- });
-
- it('should render invalid characters error message', done => {
- setTimeout(() => {
- expect(vm.$el.querySelector('p')).not.toContain(
- 'We are having trouble connecting to Docker, which could be due to an issue with your project name or path. More information',
- );
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/components/collapsible_container_spec.js b/spec/javascripts/registry/components/collapsible_container_spec.js
deleted file mode 100644
index 2a5d8dd11da..00000000000
--- a/spec/javascripts/registry/components/collapsible_container_spec.js
+++ /dev/null
@@ -1,87 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import Vue from 'vue';
-import collapsibleComponent from '~/registry/components/collapsible_container.vue';
-import store from '~/registry/stores';
-import * as types from '~/registry/stores/mutation_types';
-
-import { repoPropsData, registryServerResponse, reposServerResponse } from '../mock_data';
-
-describe('collapsible registry container', () => {
- let vm;
- let mock;
- const Component = Vue.extend(collapsibleComponent);
-
- const findDeleteBtn = () => vm.$el.querySelector('.js-remove-repo');
-
- beforeEach(() => {
- mock = new MockAdapter(axios);
-
- mock.onGet(repoPropsData.tagsPath).replyOnce(200, registryServerResponse, {});
-
- store.commit(types.SET_REPOS_LIST, reposServerResponse);
-
- vm = new Component({
- store,
- propsData: {
- repo: repoPropsData,
- },
- }).$mount();
- });
-
- afterEach(() => {
- mock.restore();
- vm.$destroy();
- });
-
- describe('toggle', () => {
- it('should be closed by default', () => {
- expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.iconName).toEqual('angle-right');
- });
-
- it('should be open when user clicks on closed repo', done => {
- vm.$el.querySelector('.js-toggle-repo').click();
-
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).not.toBeNull();
- expect(vm.iconName).toEqual('angle-up');
-
- done();
- });
- });
-
- it('should be closed when the user clicks on an opened repo', done => {
- vm.$el.querySelector('.js-toggle-repo').click();
-
- Vue.nextTick(() => {
- vm.$el.querySelector('.js-toggle-repo').click();
- setTimeout(() => {
- Vue.nextTick(() => {
- expect(vm.$el.querySelector('.container-image-tags')).toBe(null);
- expect(vm.iconName).toEqual('angle-right');
- done();
- });
- });
- });
- });
- });
-
- describe('delete repo', () => {
- it('should be possible to delete a repo', () => {
- expect(findDeleteBtn()).not.toBeNull();
- });
-
- it('should call deleteItem when confirming deletion', done => {
- findDeleteBtn().click();
- spyOn(vm, 'deleteItem').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- document.querySelector(`#${vm.modalId} .btn-danger`).click();
-
- expect(vm.deleteItem).toHaveBeenCalledWith(vm.repo);
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/components/table_registry_spec.js b/spec/javascripts/registry/components/table_registry_spec.js
deleted file mode 100644
index 9c7439206ef..00000000000
--- a/spec/javascripts/registry/components/table_registry_spec.js
+++ /dev/null
@@ -1,189 +0,0 @@
-import Vue from 'vue';
-import tableRegistry from '~/registry/components/table_registry.vue';
-import store from '~/registry/stores';
-import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper';
-import { repoPropsData } from '../mock_data';
-
-const [firstImage, secondImage] = repoPropsData.list;
-
-describe('table registry', () => {
- let vm;
- const Component = Vue.extend(tableRegistry);
- const bulkDeletePath = 'path';
-
- const findDeleteBtn = () => vm.$el.querySelector('.js-delete-registry');
- const findDeleteBtnRow = () => vm.$el.querySelector('.js-delete-registry-row');
- const findSelectAllCheckbox = () => vm.$el.querySelector('.js-select-all-checkbox > input');
- const findAllRowCheckboxes = () =>
- Array.from(vm.$el.querySelectorAll('.js-select-checkbox input'));
- const confirmationModal = (child = '') => document.querySelector(`#${vm.modalId} ${child}`);
-
- const createComponent = () => {
- vm = mountComponentWithStore(Component, {
- store,
- props: {
- repo: repoPropsData,
- },
- });
- };
-
- const selectAllCheckboxes = () => vm.selectAll();
- const deselectAllCheckboxes = () => vm.deselectAll();
-
- beforeEach(() => {
- createComponent();
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- describe('rendering', () => {
- it('should render a table with the registry list', () => {
- expect(vm.$el.querySelectorAll('table tbody tr').length).toEqual(repoPropsData.list.length);
- });
-
- it('should render registry tag', () => {
- const textRendered = vm.$el
- .querySelector('.table tbody tr')
- .textContent.trim()
- // replace additional whitespace characters (e.g. new lines) with a single empty space
- .replace(/\s\s+/g, ' ');
-
- expect(textRendered).toContain(repoPropsData.list[0].tag);
- expect(textRendered).toContain(repoPropsData.list[0].shortRevision);
- expect(textRendered).toContain(repoPropsData.list[0].layers);
- expect(textRendered).toContain(repoPropsData.list[0].size);
- });
- });
-
- describe('multi select', () => {
- it('should support multiselect and selecting a row should enable delete button', done => {
- findSelectAllCheckbox().click();
- selectAllCheckboxes();
-
- expect(findSelectAllCheckbox().checked).toBe(true);
-
- Vue.nextTick(() => {
- expect(findDeleteBtn().disabled).toBe(false);
- done();
- });
- });
-
- it('selecting all checkbox should select all rows and enable delete button', done => {
- selectAllCheckboxes();
-
- Vue.nextTick(() => {
- const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
-
- expect(checkedValues.length).toBe(repoPropsData.list.length);
- done();
- });
- });
-
- it('deselecting select all checkbox should deselect all rows and disable delete button', done => {
- selectAllCheckboxes();
- deselectAllCheckboxes();
-
- Vue.nextTick(() => {
- const checkedValues = findAllRowCheckboxes().filter(x => x.checked);
-
- expect(checkedValues.length).toBe(0);
- done();
- });
- });
-
- it('should delete multiple items when multiple items are selected', done => {
- selectAllCheckboxes();
-
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0, 1]);
- expect(findDeleteBtn().disabled).toBe(false);
-
- findDeleteBtn().click();
- spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- const modal = confirmationModal();
- confirmationModal('.btn-danger').click();
-
- expect(modal).toExist();
-
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([]);
- expect(vm.multiDeleteItems).toHaveBeenCalledWith({
- path: bulkDeletePath,
- items: [firstImage.tag, secondImage.tag],
- });
- done();
- });
- });
- });
- });
- });
-
- describe('delete registry', () => {
- beforeEach(() => {
- vm.itemsToBeDeleted = [0];
- });
-
- it('should be possible to delete a registry', done => {
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0]);
- expect(findDeleteBtn()).toBeDefined();
- expect(findDeleteBtn().disabled).toBe(false);
- expect(findDeleteBtnRow()).toBeDefined();
- done();
- });
- });
-
- it('should call deleteItems and reset itemsToBeDeleted when confirming deletion', done => {
- Vue.nextTick(() => {
- expect(vm.itemsToBeDeleted).toEqual([0]);
- expect(findDeleteBtn().disabled).toBe(false);
- findDeleteBtn().click();
- spyOn(vm, 'multiDeleteItems').and.returnValue(Promise.resolve());
-
- Vue.nextTick(() => {
- confirmationModal('.btn-danger').click();
-
- expect(vm.itemsToBeDeleted).toEqual([]);
- expect(vm.multiDeleteItems).toHaveBeenCalledWith({
- path: bulkDeletePath,
- items: [firstImage.tag],
- });
- done();
- });
- });
- });
- });
-
- describe('pagination', () => {
- it('should be possible to change the page', () => {
- expect(vm.$el.querySelector('.gl-pagination')).toBeDefined();
- });
- });
-
- describe('modal content', () => {
- it('should show the singular title and image name when deleting a single image', done => {
- findDeleteBtnRow().click();
-
- Vue.nextTick(() => {
- expect(vm.modalTitle).toBe('Remove image');
- expect(vm.modalDescription).toContain(firstImage.tag);
- done();
- });
- });
-
- it('should show the plural title and image count when deleting more than one image', done => {
- selectAllCheckboxes();
- vm.setModalDescription();
-
- Vue.nextTick(() => {
- expect(vm.modalTitle).toBe('Remove images');
- expect(vm.modalDescription).toContain('<b>2</b> images');
- done();
- });
- });
- });
-});
diff --git a/spec/javascripts/registry/stores/actions_spec.js b/spec/javascripts/registry/stores/actions_spec.js
deleted file mode 100644
index 0613ec8e0f1..00000000000
--- a/spec/javascripts/registry/stores/actions_spec.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import MockAdapter from 'axios-mock-adapter';
-import axios from '~/lib/utils/axios_utils';
-import * as actions from '~/registry/stores/actions';
-import * as types from '~/registry/stores/mutation_types';
-import state from '~/registry/stores/state';
-import { TEST_HOST } from 'spec/test_constants';
-import testAction from '../../helpers/vuex_action_helper';
-import {
- reposServerResponse,
- registryServerResponse,
- parsedReposServerResponse,
-} from '../mock_data';
-
-describe('Actions Registry Store', () => {
- let mockedState;
- let mock;
-
- beforeEach(() => {
- mockedState = state();
- mockedState.endpoint = `${TEST_HOST}/endpoint.json`;
- mock = new MockAdapter(axios);
- });
-
- afterEach(() => {
- mock.restore();
- });
-
- describe('server requests', () => {
- describe('fetchRepos', () => {
- beforeEach(() => {
- mock.onGet(`${TEST_HOST}/endpoint.json`).replyOnce(200, reposServerResponse, {});
- });
-
- it('should set receveived repos', done => {
- testAction(
- actions.fetchRepos,
- null,
- mockedState,
- [
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.TOGGLE_MAIN_LOADING },
- { type: types.SET_REPOS_LIST, payload: reposServerResponse },
- ],
- [],
- done,
- );
- });
- });
-
- describe('fetchList', () => {
- let repo;
- beforeEach(() => {
- mockedState.repos = parsedReposServerResponse;
- [, repo] = mockedState.repos;
-
- mock.onGet(repo.tagsPath).replyOnce(200, registryServerResponse, {});
- });
-
- it('should set received list', done => {
- testAction(
- actions.fetchList,
- { repo },
- mockedState,
- [
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- { type: types.TOGGLE_REGISTRY_LIST_LOADING, payload: repo },
- {
- type: types.SET_REGISTRY_LIST,
- payload: {
- repo,
- resp: registryServerResponse,
- headers: jasmine.anything(),
- },
- },
- ],
- [],
- done,
- );
- });
- });
- });
-
- describe('setMainEndpoint', () => {
- it('should commit set main endpoint', done => {
- testAction(
- actions.setMainEndpoint,
- 'endpoint',
- mockedState,
- [{ type: types.SET_MAIN_ENDPOINT, payload: 'endpoint' }],
- [],
- done,
- );
- });
- });
-
- describe('toggleLoading', () => {
- it('should commit toggle main loading', done => {
- testAction(
- actions.toggleLoading,
- null,
- mockedState,
- [{ type: types.TOGGLE_MAIN_LOADING }],
- [],
- done,
- );
- });
- });
-
- describe('deleteItem', () => {
- it('should perform DELETE request on destroyPath', done => {
- const destroyPath = `${TEST_HOST}/mygroup/myproject/container_registry/1.json`;
- let deleted = false;
- mock.onDelete(destroyPath).replyOnce(() => {
- deleted = true;
- return [200];
- });
- testAction(
- actions.deleteItem,
- {
- destroyPath,
- },
- mockedState,
- )
- .then(() => {
- expect(mock.history.delete.length).toBe(1);
- expect(deleted).toBe(true);
- done();
- })
- .catch(done.fail);
- });
- });
-});
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index df6263eeed2..06f454808e3 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -546,7 +546,7 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do
before do
expect(Clusters::KubernetesNamespaceFinder).to receive(:new)
- .with(cluster, project: environment.project, environment_slug: environment.slug)
+ .with(cluster, project: environment.project, environment_name: environment.name)
.and_return(double(execute: persisted_namespace))
end
diff --git a/spec/models/clusters/kubernetes_namespace_spec.rb b/spec/models/clusters/kubernetes_namespace_spec.rb
index d4e3a0ac84d..2920bbf2b58 100644
--- a/spec/models/clusters/kubernetes_namespace_spec.rb
+++ b/spec/models/clusters/kubernetes_namespace_spec.rb
@@ -24,13 +24,13 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
end
end
- describe '.with_environment_slug' do
+ describe '.with_environment_name' do
let(:cluster) { create(:cluster, :group) }
- let(:environment) { create(:environment, slug: slug) }
+ let(:environment) { create(:environment, name: name) }
- let(:slug) { 'production' }
+ let(:name) { 'production' }
- subject { described_class.with_environment_slug(slug) }
+ subject { described_class.with_environment_name(name) }
context 'there is no associated environment' do
let!(:namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: environment.project) }
@@ -48,12 +48,12 @@ RSpec.describe Clusters::KubernetesNamespace, type: :model do
)
end
- context 'with a matching slug' do
+ context 'with a matching name' do
it { is_expected.to eq [namespace] }
end
- context 'without a matching slug' do
- let(:environment) { create(:environment, slug: 'staging') }
+ context 'without a matching name' do
+ let(:environment) { create(:environment, name: 'staging') }
it { is_expected.to be_empty }
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 0c4cf291d20..64de6a8ab9b 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -218,7 +218,7 @@ describe Clusters::Platforms::Kubernetes do
before do
allow(Clusters::KubernetesNamespaceFinder).to receive(:new)
- .with(cluster, project: project, environment_slug: environment_slug)
+ .with(cluster, project: project, environment_name: environment_name)
.and_return(double(execute: persisted_namespace))
end