Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-04-14 18:15:22 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-04-14 18:15:22 +0300
commitcdb41961fd2bc233d36c5b30f89d087c2efa9818 (patch)
tree9903f8054fc1f8681c670984a680b768d139a251 /spec/frontend/ci/artifacts/components
parent8a5138ed7d38ccff8b5ca2fe0f7bbb77f8fdaad3 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/ci/artifacts/components')
-rw-r--r--spec/frontend/ci/artifacts/components/artifact_row_spec.js6
-rw-r--r--spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js68
-rw-r--r--spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js226
3 files changed, 215 insertions, 85 deletions
diff --git a/spec/frontend/ci/artifacts/components/artifact_row_spec.js b/spec/frontend/ci/artifacts/components/artifact_row_spec.js
index 86875cd8566..f64d32410ed 100644
--- a/spec/frontend/ci/artifacts/components/artifact_row_spec.js
+++ b/spec/frontend/ci/artifacts/components/artifact_row_spec.js
@@ -1,4 +1,4 @@
-import { GlBadge, GlButton, GlFriendlyWrap, GlFormCheckbox } from '@gitlab/ui';
+import { GlBadge, GlFriendlyWrap, GlFormCheckbox } from '@gitlab/ui';
import mockGetJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
import { numberToHumanSize } from '~/lib/utils/number_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
@@ -27,7 +27,7 @@ describe('ArtifactRow component', () => {
isLastRow: false,
},
provide: { canDestroyArtifacts, glFeatures },
- stubs: { GlBadge, GlButton, GlFriendlyWrap },
+ stubs: { GlBadge, GlFriendlyWrap },
});
};
@@ -70,7 +70,7 @@ describe('ArtifactRow component', () => {
expect(wrapper.emitted('delete')).toBeUndefined();
- findDeleteButton().trigger('click');
+ findDeleteButton().vm.$emit('click');
await waitForPromises();
expect(wrapper.emitted('delete')).toBeDefined();
diff --git a/spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js b/spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js
index dd077dcac9f..9e4fa6b9c6f 100644
--- a/spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js
+++ b/spec/frontend/ci/artifacts/components/artifacts_bulk_delete_spec.js
@@ -1,20 +1,11 @@
-import { GlSprintf, GlModal } from '@gitlab/ui';
-import Vue from 'vue';
-import VueApollo from 'vue-apollo';
+import { GlSprintf } from '@gitlab/ui';
import mockGetJobArtifactsResponse from 'test_fixtures/graphql/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ArtifactsBulkDelete from '~/ci/artifacts/components/artifacts_bulk_delete.vue';
-import bulkDestroyArtifactsMutation from '~/ci/artifacts/graphql/mutations/bulk_destroy_job_artifacts.mutation.graphql';
-
-Vue.use(VueApollo);
describe('ArtifactsBulkDelete component', () => {
let wrapper;
- let requestHandlers;
- const projectId = '123';
const selectedArtifacts = [
mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes[0].id,
mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes[1].id,
@@ -23,74 +14,35 @@ describe('ArtifactsBulkDelete component', () => {
const findText = () => wrapper.findComponent(GlSprintf).text();
const findDeleteButton = () => wrapper.findByTestId('bulk-delete-delete-button');
const findClearButton = () => wrapper.findByTestId('bulk-delete-clear-button');
- const findModal = () => wrapper.findComponent(GlModal);
- const createComponent = ({
- handlers = {
- bulkDestroyArtifactsMutation: jest.fn(),
- },
- } = {}) => {
- requestHandlers = handlers;
- wrapper = mountExtended(ArtifactsBulkDelete, {
- apolloProvider: createMockApollo([
- [bulkDestroyArtifactsMutation, requestHandlers.bulkDestroyArtifactsMutation],
- ]),
+ const createComponent = () => {
+ wrapper = shallowMountExtended(ArtifactsBulkDelete, {
propsData: {
selectedArtifacts,
- queryVariables: {},
- isLoading: false,
- isLastRow: false,
},
- provide: { projectId },
+ stubs: { GlSprintf },
});
};
describe('selected artifacts box', () => {
- beforeEach(async () => {
+ beforeEach(() => {
createComponent();
- await waitForPromises();
});
it('displays selected artifacts count', () => {
expect(findText()).toContain(String(selectedArtifacts.length));
});
- it('opens the confirmation modal when the delete button is clicked', async () => {
- expect(findModal().props('visible')).toBe(false);
-
- findDeleteButton().trigger('click');
- await waitForPromises();
+ it('emits showBulkDeleteModal event when the delete button is clicked', () => {
+ findDeleteButton().vm.$emit('click');
- expect(findModal().props('visible')).toBe(true);
+ expect(wrapper.emitted('showBulkDeleteModal')).toBeDefined();
});
it('emits clearSelectedArtifacts event when the clear button is clicked', () => {
- findClearButton().trigger('click');
+ findClearButton().vm.$emit('click');
expect(wrapper.emitted('clearSelectedArtifacts')).toBeDefined();
});
});
-
- describe('bulk delete confirmation modal', () => {
- beforeEach(async () => {
- createComponent();
- findDeleteButton().trigger('click');
- await waitForPromises();
- });
-
- it('calls the bulk delete mutation with the selected artifacts on confirm', () => {
- findModal().vm.$emit('primary');
-
- expect(requestHandlers.bulkDestroyArtifactsMutation).toHaveBeenCalledWith({
- projectId: `gid://gitlab/Project/${projectId}`,
- ids: selectedArtifacts,
- });
- });
-
- it('does not call the bulk delete mutation on cancel', () => {
- findModal().vm.$emit('cancel');
-
- expect(requestHandlers.bulkDestroyArtifactsMutation).not.toHaveBeenCalled();
- });
- });
});
diff --git a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
index 2855b0ecb37..74d0d683662 100644
--- a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
@@ -17,16 +17,20 @@ import FeedbackBanner from '~/ci/artifacts/components/feedback_banner.vue';
import ArtifactsTableRowDetails from '~/ci/artifacts/components/artifacts_table_row_details.vue';
import ArtifactDeleteModal from '~/ci/artifacts/components/artifact_delete_modal.vue';
import ArtifactsBulkDelete from '~/ci/artifacts/components/artifacts_bulk_delete.vue';
+import BulkDeleteModal from '~/ci/artifacts/components/bulk_delete_modal.vue';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import getJobArtifactsQuery from '~/ci/artifacts/graphql/queries/get_job_artifacts.query.graphql';
-import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import bulkDestroyArtifactsMutation from '~/ci/artifacts/graphql/mutations/bulk_destroy_job_artifacts.mutation.graphql';
+import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
+import { TYPENAME_PROJECT } from '~/graphql_shared/constants';
import {
ARCHIVE_FILE_TYPE,
JOBS_PER_PAGE,
I18N_FETCH_ERROR,
INITIAL_CURRENT_PAGE,
BULK_DELETE_FEATURE_FLAG,
+ I18N_BULK_DELETE_ERROR,
} from '~/ci/artifacts/constants';
import { totalArtifactsSizeForJob } from '~/ci/artifacts/utils';
import { createAlert } from '~/alert';
@@ -52,7 +56,8 @@ describe('JobArtifactsTable component', () => {
const findCount = () => wrapper.findByTestId('job-artifacts-count');
const findCountAt = (i) => wrapper.findAllByTestId('job-artifacts-count').at(i);
- const findModal = () => wrapper.findComponent(GlModal);
+ const findDeleteModal = () => wrapper.findComponent(ArtifactDeleteModal);
+ const findBulkDeleteModal = () => wrapper.findComponent(BulkDeleteModal);
const findStatuses = () => wrapper.findAllByTestId('job-artifacts-job-status');
const findSuccessfulJobStatus = () => findStatuses().at(0);
@@ -73,9 +78,10 @@ describe('JobArtifactsTable component', () => {
const findArtifactDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
// first checkbox is a "select all", this finder should get the first job checkbox
- const findJobCheckbox = () => wrapper.findAllComponents(GlFormCheckbox).at(1);
+ const findJobCheckbox = (i = 1) => wrapper.findAllComponents(GlFormCheckbox).at(i);
const findAnyCheckbox = () => wrapper.findComponent(GlFormCheckbox);
const findBulkDelete = () => wrapper.findComponent(ArtifactsBulkDelete);
+ const findBulkDeleteContainer = () => wrapper.findByTestId('bulk-delete-container');
const findPagination = () => wrapper.findComponent(GlPagination);
const setPage = async (page) => {
@@ -83,6 +89,8 @@ describe('JobArtifactsTable component', () => {
await waitForPromises();
};
+ const projectId = 'some/project/id';
+
let enoughJobsToPaginate = [...getJobArtifactsResponse.data.project.jobs.nodes];
while (enoughJobsToPaginate.length <= JOBS_PER_PAGE) {
enoughJobsToPaginate = [
@@ -105,10 +113,20 @@ describe('JobArtifactsTable component', () => {
const archiveArtifact = job.artifacts.nodes.find(
(artifact) => artifact.fileType === ARCHIVE_FILE_TYPE,
);
+ const job2 = getJobArtifactsResponse.data.project.jobs.nodes[1];
+
+ const destroyedCount = job.artifacts.nodes.length;
+ const destroyedIds = job.artifacts.nodes.map((node) => node.id);
+ const bulkDestroyMutationHandler = jest.fn().mockResolvedValue({
+ data: {
+ bulkDestroyJobArtifacts: { errors: [], destroyedCount, destroyedIds },
+ },
+ });
const createComponent = ({
handlers = {
getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
+ bulkDestroyArtifactsMutation: bulkDestroyMutationHandler,
},
data = {},
canDestroyArtifacts = true,
@@ -118,10 +136,11 @@ describe('JobArtifactsTable component', () => {
wrapper = mountExtended(JobArtifactsTable, {
apolloProvider: createMockApollo([
[getJobArtifactsQuery, requestHandlers.getJobArtifactsQuery],
+ [bulkDestroyArtifactsMutation, requestHandlers.bulkDestroyArtifactsMutation],
]),
provide: {
projectPath: 'project/path',
- projectId: 'gid://projects/id',
+ projectId,
canDestroyArtifacts,
artifactsManagementFeedbackImagePath: 'banner/image/path',
glFeatures,
@@ -265,12 +284,12 @@ describe('JobArtifactsTable component', () => {
expect(findDetailsInRow(0).exists()).toBe(false);
expect(findDetailsInRow(1).exists()).toBe(true);
- findArtifactDeleteButton().trigger('click');
+ findArtifactDeleteButton().vm.$emit('click');
await waitForPromises();
- expect(findModal().props('visible')).toBe(true);
+ expect(findDeleteModal().findComponent(GlModal).props('visible')).toBe(true);
- wrapper.findComponent(ArtifactDeleteModal).vm.$emit('primary');
+ findDeleteModal().vm.$emit('primary');
await waitForPromises();
expect(findDetailsInRow(0).exists()).toBe(false);
@@ -332,24 +351,132 @@ describe('JobArtifactsTable component', () => {
});
describe('delete button', () => {
- it('does not show when user does not have permission', async () => {
- createComponent({ canDestroyArtifacts: false });
+ const artifactsFromJob = job.artifacts.nodes.map((node) => node.id);
+
+ describe('with delete permission and bulk delete feature flag enabled', () => {
+ beforeEach(async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
+ });
+
+ await waitForPromises();
+ });
+
+ it('opens the confirmation modal with the artifacts from the job', async () => {
+ await findDeleteButton().vm.$emit('click');
+
+ expect(findBulkDeleteModal().props()).toMatchObject({
+ visible: true,
+ artifactsToDelete: artifactsFromJob,
+ });
+ });
+
+ it('on confirm, deletes the artifacts from the job and shows a toast', async () => {
+ findDeleteButton().vm.$emit('click');
+ findBulkDeleteModal().vm.$emit('primary');
+
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: artifactsFromJob,
+ });
+
+ await waitForPromises();
+
+ expect(mockToastShow).toHaveBeenCalledWith(
+ `${artifactsFromJob.length} selected artifacts deleted`,
+ );
+ });
+ it('does not clear selected artifacts on success', async () => {
+ // select job 2 via checkbox
+ findJobCheckbox(2).vm.$emit('input', true);
+
+ // click delete button job 1
+ findDeleteButton().vm.$emit('click');
+
+ // job 2's artifacts should still be selected
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job2.artifacts.nodes.map((node) => node.id),
+ );
+
+ // confirm delete
+ findBulkDeleteModal().vm.$emit('primary');
+
+ // job 1's artifacts should be deleted
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: artifactsFromJob,
+ });
+
+ await waitForPromises();
+
+ // job 2's artifacts should still be selected
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job2.artifacts.nodes.map((node) => node.id),
+ );
+ });
+ });
+
+ it('shows an alert and does not clear selected artifacts on error', async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
+ handlers: {
+ getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
+ bulkDestroyArtifactsMutation: jest.fn().mockRejectedValue(),
+ },
+ });
await waitForPromises();
- expect(findDeleteButton().exists()).toBe(false);
+ // select job 2 via checkbox
+ findJobCheckbox(2).vm.$emit('input', true);
+
+ // click delete button job 1
+ findDeleteButton().vm.$emit('click');
+
+ // confirm delete
+ findBulkDeleteModal().vm.$emit('primary');
+
+ await waitForPromises();
+
+ // job 2's artifacts should still be selected
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job2.artifacts.nodes.map((node) => node.id),
+ );
+ expect(createAlert).toHaveBeenCalledWith({
+ captureError: true,
+ error: expect.any(Error),
+ message: I18N_BULK_DELETE_ERROR,
+ });
});
- it('shows a disabled delete button for now (coming soon)', async () => {
- createComponent();
+ it('is disabled when bulk delete feature flag is disabled', async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
+ });
await waitForPromises();
expect(findDeleteButton().attributes('disabled')).toBe('disabled');
});
+
+ it('is hidden when user does not have delete permission', async () => {
+ createComponent({
+ canDestroyArtifacts: false,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
+ });
+
+ await waitForPromises();
+
+ expect(findDeleteButton().exists()).toBe(false);
+ });
});
describe('bulk delete', () => {
+ const selectedArtifacts = job.artifacts.nodes.map((node) => node.id);
+
describe('with permission and feature flag enabled', () => {
beforeEach(async () => {
createComponent({
@@ -361,33 +488,84 @@ describe('JobArtifactsTable component', () => {
});
it('shows selected artifacts when a job is checked', async () => {
- expect(findBulkDelete().exists()).toBe(false);
+ expect(findBulkDeleteContainer().exists()).toBe(false);
await findJobCheckbox().vm.$emit('input', true);
- expect(findBulkDelete().exists()).toBe(true);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
- job.artifacts.nodes.map((node) => node.id),
- );
+ expect(findBulkDeleteContainer().exists()).toBe(true);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(selectedArtifacts);
});
it('disappears when selected artifacts are cleared', async () => {
await findJobCheckbox().vm.$emit('input', true);
- expect(findBulkDelete().exists()).toBe(true);
+ expect(findBulkDeleteContainer().exists()).toBe(true);
await findBulkDelete().vm.$emit('clearSelectedArtifacts');
- expect(findBulkDelete().exists()).toBe(false);
+ expect(findBulkDeleteContainer().exists()).toBe(false);
});
- it('shows a toast when artifacts are deleted', async () => {
- const count = job.artifacts.nodes.length;
+ it('shows a modal to confirm bulk delete', async () => {
+ findJobCheckbox().vm.$emit('input', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
- await findJobCheckbox().vm.$emit('input', true);
- findBulkDelete().vm.$emit('deleted', count);
+ await waitForPromises();
+
+ expect(findBulkDeleteModal().props('visible')).toBe(true);
+ });
+
+ it('deletes the selected artifacts and shows a toast', async () => {
+ findJobCheckbox().vm.$emit('input', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
+ findBulkDeleteModal().vm.$emit('primary');
+
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: selectedArtifacts,
+ });
+
+ await waitForPromises();
+
+ expect(mockToastShow).toHaveBeenCalledWith(
+ `${selectedArtifacts.length} selected artifacts deleted`,
+ );
+ });
+
+ it('clears selected artifacts on success', async () => {
+ findJobCheckbox().vm.$emit('input', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
+ findBulkDeleteModal().vm.$emit('primary');
+
+ await waitForPromises();
+
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
+ });
+ });
+
+ it('shows an alert and does not clear selected artifacts on error', async () => {
+ createComponent({
+ canDestroyArtifacts: true,
+ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
+ handlers: {
+ getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
+ bulkDestroyArtifactsMutation: jest.fn().mockRejectedValue(),
+ },
+ });
+
+ await waitForPromises();
+
+ findJobCheckbox().vm.$emit('input', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
+ findBulkDeleteModal().vm.$emit('primary');
+
+ await waitForPromises();
- expect(mockToastShow).toHaveBeenCalledWith(`${count} selected artifacts deleted`);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(selectedArtifacts);
+ expect(createAlert).toHaveBeenCalledWith({
+ captureError: true,
+ error: expect.any(Error),
+ message: I18N_BULK_DELETE_ERROR,
});
});