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>2022-10-20 18:10:58 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-10-20 18:10:58 +0300
commit049d16d168fdee408b78f5f38619c092fd3b2265 (patch)
tree22d1db5ab4fae0967a4da4b1a6b097ef9e5d7aa2 /spec/frontend/artifacts
parentbf18f3295b550c564086efd0a32d9a25435ce216 (diff)
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/artifacts')
-rw-r--r--spec/frontend/artifacts/components/artifact_row_spec.js67
-rw-r--r--spec/frontend/artifacts/components/artifacts_table_row_details_spec.js107
-rw-r--r--spec/frontend/artifacts/components/job_artifacts_table_spec.js222
-rw-r--r--spec/frontend/artifacts/graphql/cache_update_spec.js67
4 files changed, 463 insertions, 0 deletions
diff --git a/spec/frontend/artifacts/components/artifact_row_spec.js b/spec/frontend/artifacts/components/artifact_row_spec.js
new file mode 100644
index 00000000000..ccde3bbbf98
--- /dev/null
+++ b/spec/frontend/artifacts/components/artifact_row_spec.js
@@ -0,0 +1,67 @@
+import { GlBadge, GlButton } from '@gitlab/ui';
+import mockGetJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
+import { numberToHumanSize } from '~/lib/utils/number_utils';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import ArtifactRow from '~/artifacts/components/artifact_row.vue';
+
+describe('ArtifactRow component', () => {
+ let wrapper;
+
+ const artifact = mockGetJobArtifactsResponse.data.project.jobs.nodes[0].artifacts.nodes[0];
+
+ const findName = () => wrapper.findByTestId('job-artifact-row-name');
+ const findBadge = () => wrapper.findComponent(GlBadge);
+ const findSize = () => wrapper.findByTestId('job-artifact-row-size');
+ const findDownloadButton = () => wrapper.findByTestId('job-artifact-row-download-button');
+ const findDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
+
+ const createComponent = (mountFn = shallowMountExtended) => {
+ wrapper = mountFn(ArtifactRow, {
+ propsData: {
+ artifact,
+ isLoading: false,
+ isLastRow: false,
+ },
+ stubs: { GlBadge, GlButton },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('artifact details', () => {
+ beforeEach(async () => {
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('displays the artifact name and type', () => {
+ expect(findName().text()).toContain(artifact.name);
+ expect(findBadge().text()).toBe(artifact.fileType.toLowerCase());
+ });
+
+ it('displays the artifact size', () => {
+ expect(findSize().text()).toBe(numberToHumanSize(artifact.size));
+ });
+
+ it('displays the download button as a link to the download path', () => {
+ expect(findDownloadButton().attributes('href')).toBe(artifact.downloadPath);
+ });
+
+ it('displays the delete button', () => {
+ expect(findDeleteButton().exists()).toBe(true);
+ });
+
+ it('emits the delete event when the delete button is clicked', async () => {
+ expect(wrapper.emitted('delete')).toBeUndefined();
+
+ findDeleteButton().trigger('click');
+ await waitForPromises();
+
+ expect(wrapper.emitted('delete')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
new file mode 100644
index 00000000000..4834adeea1e
--- /dev/null
+++ b/spec/frontend/artifacts/components/artifacts_table_row_details_spec.js
@@ -0,0 +1,107 @@
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
+import waitForPromises from 'helpers/wait_for_promises';
+import ArtifactsTableRowDetails from '~/artifacts/components/artifacts_table_row_details.vue';
+import ArtifactRow from '~/artifacts/components/artifact_row.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import destroyArtifactMutation from '~/artifacts/graphql/mutations/destroy_artifact.mutation.graphql';
+import { I18N_DESTROY_ERROR } from '~/artifacts/constants';
+import { createAlert } from '~/flash';
+
+jest.mock('~/flash');
+
+const { artifacts } = getJobArtifactsResponse.data.project.jobs.nodes[0];
+const refetchArtifacts = jest.fn();
+
+Vue.use(VueApollo);
+
+describe('ArtifactsTableRowDetails component', () => {
+ let wrapper;
+ let requestHandlers;
+
+ const createComponent = (
+ handlers = {
+ destroyArtifactMutation: jest.fn(),
+ },
+ ) => {
+ requestHandlers = handlers;
+ wrapper = mountExtended(ArtifactsTableRowDetails, {
+ apolloProvider: createMockApollo([
+ [destroyArtifactMutation, requestHandlers.destroyArtifactMutation],
+ ]),
+ propsData: {
+ artifacts,
+ refetchArtifacts,
+ queryVariables: {},
+ },
+ data() {
+ return { deletingArtifactId: null };
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('passes correct props', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('to the artifact rows', () => {
+ [0, 1, 2].forEach((index) => {
+ expect(wrapper.findAllComponents(ArtifactRow).at(index).props()).toMatchObject({
+ artifact: artifacts.nodes[index],
+ isLoading: false,
+ });
+ });
+ });
+ });
+
+ describe('when an artifact row emits the delete event', () => {
+ it('sets isLoading to true for that row', async () => {
+ createComponent();
+ await waitForPromises();
+
+ wrapper.findComponent(ArtifactRow).vm.$emit('delete');
+
+ await nextTick();
+
+ [
+ { index: 0, expectedLoading: true },
+ { index: 1, expectedLoading: false },
+ ].forEach(({ index, expectedLoading }) => {
+ expect(wrapper.findAllComponents(ArtifactRow).at(index).props('isLoading')).toBe(
+ expectedLoading,
+ );
+ });
+ });
+
+ it('triggers the destroyArtifact GraphQL mutation', async () => {
+ createComponent();
+ await waitForPromises();
+
+ wrapper.findComponent(ArtifactRow).vm.$emit('delete');
+
+ expect(requestHandlers.destroyArtifactMutation).toHaveBeenCalled();
+ });
+
+ it('displays a flash message and refetches artifacts when the mutation fails', async () => {
+ createComponent({
+ destroyArtifactMutation: jest.fn().mockRejectedValue(new Error('Error!')),
+ });
+ await waitForPromises();
+
+ expect(wrapper.emitted('refetch')).toBeUndefined();
+
+ wrapper.findComponent(ArtifactRow).vm.$emit('delete');
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({ message: I18N_DESTROY_ERROR });
+ expect(wrapper.emitted('refetch')).toBeDefined();
+ });
+ });
+});
diff --git a/spec/frontend/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
new file mode 100644
index 00000000000..6c3a56e5d5c
--- /dev/null
+++ b/spec/frontend/artifacts/components/job_artifacts_table_spec.js
@@ -0,0 +1,222 @@
+import { GlLoadingIcon, GlTable, GlLink, GlBadge, GlPagination } from '@gitlab/ui';
+import Vue from 'vue';
+import VueApollo from 'vue-apollo';
+import getJobArtifactsResponse from 'test_fixtures/graphql/artifacts/graphql/queries/get_job_artifacts.query.graphql.json';
+import CiIcon from '~/vue_shared/components/ci_icon.vue';
+import waitForPromises from 'helpers/wait_for_promises';
+import JobArtifactsTable from '~/artifacts/components/job_artifacts_table.vue';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
+import getJobArtifactsQuery from '~/artifacts/graphql/queries/get_job_artifacts.query.graphql';
+import destroyArtifactMutation from '~/artifacts/graphql/mutations/destroy_artifact.mutation.graphql';
+import { getIdFromGraphQLId } from '~/graphql_shared/utils';
+import { ARCHIVE_FILE_TYPE, JOBS_PER_PAGE, I18N_FETCH_ERROR } from '~/artifacts/constants';
+import { totalArtifactsSizeForJob } from '~/artifacts/utils';
+import { createAlert } from '~/flash';
+
+jest.mock('~/flash');
+
+Vue.use(VueApollo);
+
+describe('JobArtifactsTable component', () => {
+ let wrapper;
+ let requestHandlers;
+
+ const findLoadingState = () => wrapper.findComponent(GlLoadingIcon);
+ const findTable = () => wrapper.findComponent(GlTable);
+ const findCount = () => wrapper.findByTestId('job-artifacts-count');
+
+ const findStatuses = () => wrapper.findAllByTestId('job-artifacts-job-status');
+ const findSuccessfulJobStatus = () => findStatuses().at(0);
+ const findFailedJobStatus = () => findStatuses().at(1);
+
+ const findLinks = () => wrapper.findAllComponents(GlLink);
+ const findJobLink = () => findLinks().at(0);
+ const findPipelineLink = () => findLinks().at(1);
+ const findRefLink = () => findLinks().at(2);
+ const findCommitLink = () => findLinks().at(3);
+
+ const findSize = () => wrapper.findByTestId('job-artifacts-size');
+ const findCreated = () => wrapper.findByTestId('job-artifacts-created');
+
+ const findDownloadButton = () => wrapper.findByTestId('job-artifacts-download-button');
+ const findBrowseButton = () => wrapper.findByTestId('job-artifacts-browse-button');
+ const findDeleteButton = () => wrapper.findByTestId('job-artifacts-delete-button');
+
+ const findPagination = () => wrapper.findComponent(GlPagination);
+ const setPage = async (page) => {
+ findPagination().vm.$emit('input', page);
+ await waitForPromises();
+ };
+
+ let enoughJobsToPaginate = [...getJobArtifactsResponse.data.project.jobs.nodes];
+ while (enoughJobsToPaginate.length <= JOBS_PER_PAGE) {
+ enoughJobsToPaginate = [
+ ...enoughJobsToPaginate,
+ ...getJobArtifactsResponse.data.project.jobs.nodes,
+ ];
+ }
+ const getJobArtifactsResponseThatPaginates = {
+ data: { project: { jobs: { nodes: enoughJobsToPaginate } } },
+ };
+
+ const createComponent = (
+ handlers = {
+ getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
+ destroyArtifactMutation: jest.fn(),
+ },
+ data = {},
+ ) => {
+ requestHandlers = handlers;
+ wrapper = mountExtended(JobArtifactsTable, {
+ apolloProvider: createMockApollo([
+ [getJobArtifactsQuery, requestHandlers.getJobArtifactsQuery],
+ [destroyArtifactMutation, requestHandlers.destroyArtifactMutation],
+ ]),
+ provide: { projectPath: 'project/path' },
+ data() {
+ return data;
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('when loading, shows a loading state', () => {
+ createComponent();
+
+ expect(findLoadingState().exists()).toBe(true);
+ });
+
+ it('on error, shows an alert', async () => {
+ createComponent({
+ getJobArtifactsQuery: jest.fn().mockRejectedValue(new Error('Error!')),
+ });
+
+ await waitForPromises();
+
+ expect(createAlert).toHaveBeenCalledWith({ message: I18N_FETCH_ERROR });
+ });
+
+ it('with data, renders the table', async () => {
+ createComponent();
+
+ await waitForPromises();
+
+ expect(findTable().exists()).toBe(true);
+ });
+
+ describe('job details', () => {
+ const job = getJobArtifactsResponse.data.project.jobs.nodes[0];
+ const archiveArtifact = job.artifacts.nodes.find(
+ (artifact) => artifact.fileType === ARCHIVE_FILE_TYPE,
+ );
+
+ beforeEach(async () => {
+ createComponent();
+
+ await waitForPromises();
+ });
+
+ it('shows the artifact count', () => {
+ expect(findCount().text()).toBe(`${job.artifacts.nodes.length} files`);
+ });
+
+ it('expands to show the list of artifacts', async () => {
+ jest.spyOn(wrapper.vm, 'handleRowToggle');
+
+ findCount().trigger('click');
+
+ expect(wrapper.vm.handleRowToggle).toHaveBeenCalled();
+ });
+
+ it('shows the job status as an icon for a successful job', () => {
+ expect(findSuccessfulJobStatus().findComponent(CiIcon).exists()).toBe(true);
+ expect(findSuccessfulJobStatus().findComponent(GlBadge).exists()).toBe(false);
+ });
+
+ it('shows the job status as a badge for other job statuses', () => {
+ expect(findFailedJobStatus().findComponent(GlBadge).exists()).toBe(true);
+ expect(findFailedJobStatus().findComponent(CiIcon).exists()).toBe(false);
+ });
+
+ it('shows links to the job, pipeline, ref, and commit', () => {
+ expect(findJobLink().text()).toBe(job.name);
+ expect(findJobLink().attributes('href')).toBe(job.webPath);
+
+ expect(findPipelineLink().text()).toBe(`#${getIdFromGraphQLId(job.pipeline.id)}`);
+ expect(findPipelineLink().attributes('href')).toBe(job.pipeline.path);
+
+ expect(findRefLink().text()).toBe(job.refName);
+ expect(findRefLink().attributes('href')).toBe(job.refPath);
+
+ expect(findCommitLink().text()).toBe(job.shortSha);
+ expect(findCommitLink().attributes('href')).toBe(job.commitPath);
+ });
+
+ it('shows the total size of artifacts', () => {
+ expect(findSize().text()).toBe(totalArtifactsSizeForJob(job));
+ });
+
+ it('shows the created time', () => {
+ expect(findCreated().text()).toBe('5 years ago');
+ });
+
+ it('shows the download, browse, and delete buttons', () => {
+ expect(findDownloadButton().attributes('href')).toBe(archiveArtifact.downloadPath);
+ expect(findBrowseButton().attributes('disabled')).toBe('disabled');
+ expect(findDeleteButton().attributes('disabled')).toBe('disabled');
+ });
+ });
+
+ describe('pagination', () => {
+ const { pageInfo } = getJobArtifactsResponse.data.project.jobs;
+
+ beforeEach(async () => {
+ createComponent(
+ {
+ getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponseThatPaginates),
+ },
+ {
+ jobArtifacts: {
+ count: enoughJobsToPaginate.length,
+ pageInfo,
+ },
+ },
+ );
+
+ await waitForPromises();
+ });
+
+ it('renders pagination and passes page props', () => {
+ expect(findPagination().exists()).toBe(true);
+ expect(findPagination().props()).toMatchObject({
+ value: wrapper.vm.pagination.currentPage,
+ prevPage: wrapper.vm.prevPage,
+ nextPage: wrapper.vm.nextPage,
+ });
+ });
+
+ it('updates query variables when going to previous page', () => {
+ return setPage(1).then(() => {
+ expect(wrapper.vm.queryVariables).toMatchObject({
+ projectPath: 'project/path',
+ nextPageCursor: undefined,
+ prevPageCursor: pageInfo.startCursor,
+ });
+ });
+ });
+
+ it('updates query variables when going to next page', () => {
+ return setPage(2).then(() => {
+ expect(wrapper.vm.queryVariables).toMatchObject({
+ lastPageSize: null,
+ nextPageCursor: pageInfo.endCursor,
+ prevPageCursor: '',
+ });
+ });
+ });
+ });
+});
diff --git a/spec/frontend/artifacts/graphql/cache_update_spec.js b/spec/frontend/artifacts/graphql/cache_update_spec.js
new file mode 100644
index 00000000000..4d610328298
--- /dev/null
+++ b/spec/frontend/artifacts/graphql/cache_update_spec.js
@@ -0,0 +1,67 @@
+import getJobArtifactsQuery from '~/artifacts/graphql/queries/get_job_artifacts.query.graphql';
+import { removeArtifactFromStore } from '~/artifacts/graphql/cache_update';
+
+describe('Artifact table cache updates', () => {
+ let store;
+
+ const cacheMock = {
+ project: {
+ jobs: {
+ nodes: [
+ { artifacts: { nodes: [{ id: 'foo' }] } },
+ { artifacts: { nodes: [{ id: 'bar' }] } },
+ ],
+ },
+ },
+ };
+
+ const query = getJobArtifactsQuery;
+ const variables = { fullPath: 'path/to/project' };
+
+ beforeEach(() => {
+ store = {
+ readQuery: jest.fn().mockReturnValue(cacheMock),
+ writeQuery: jest.fn(),
+ };
+ });
+
+ describe('removeArtifactFromStore', () => {
+ it('calls readQuery', () => {
+ removeArtifactFromStore(store, 'foo', query, variables);
+ expect(store.readQuery).toHaveBeenCalledWith({ query, variables });
+ });
+
+ it('writes the correct result in the cache', () => {
+ removeArtifactFromStore(store, 'foo', query, variables);
+ expect(store.writeQuery).toHaveBeenCalledWith({
+ query,
+ variables,
+ data: {
+ project: {
+ jobs: {
+ nodes: [{ artifacts: { nodes: [] } }, { artifacts: { nodes: [{ id: 'bar' }] } }],
+ },
+ },
+ },
+ });
+ });
+
+ it('does not remove an unknown artifact', () => {
+ removeArtifactFromStore(store, 'baz', query, variables);
+ expect(store.writeQuery).toHaveBeenCalledWith({
+ query,
+ variables,
+ data: {
+ project: {
+ jobs: {
+ nodes: [
+ { artifacts: { nodes: [{ id: 'foo' }] } },
+ { artifacts: { nodes: [{ id: 'bar' }] } },
+ ],
+ },
+ },
+ },
+ });
+ });
+ });
+});