diff options
Diffstat (limited to 'spec/frontend/releases')
22 files changed, 515 insertions, 567 deletions
diff --git a/spec/frontend/releases/__snapshots__/util_spec.js.snap b/spec/frontend/releases/__snapshots__/util_spec.js.snap index f56e296d106..84247e2a5a0 100644 --- a/spec/frontend/releases/__snapshots__/util_spec.js.snap +++ b/spec/frontend/releases/__snapshots__/util_spec.js.snap @@ -5,109 +5,123 @@ Object { "data": Array [ Object { "_links": Object { - "editUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit", - "issuesUrl": null, - "mergeRequestsUrl": null, - "self": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10", - "selfUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10", + "editUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/edit", + "issuesUrl": "http://localhost/releases-namespace/releases-project/-/issues?release_tag=v1.1&scope=all&state=opened", + "mergeRequestsUrl": "http://localhost/releases-namespace/releases-project/-/merge_requests?release_tag=v1.1&scope=all&state=opened", + "self": "http://localhost/releases-namespace/releases-project/-/releases/v1.1", + "selfUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1", }, "assets": Object { - "count": 7, + "count": 8, "links": Array [ Object { - "directAssetUrl": "http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook", + "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-3", "external": true, - "id": "gid://gitlab/Releases::Link/69", - "linkType": "other", - "name": "An example link", - "url": "https://example.com/link", + "id": "gid://gitlab/Releases::Link/13", + "linkType": "image", + "name": "Image", + "url": "https://example.com/image", }, Object { - "directAssetUrl": "https://example.com/package", + "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-2", "external": true, - "id": "gid://gitlab/Releases::Link/68", + "id": "gid://gitlab/Releases::Link/12", "linkType": "package", - "name": "An example package link", + "name": "Package", "url": "https://example.com/package", }, Object { - "directAssetUrl": "https://example.com/image", + "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/awesome-app-1", + "external": false, + "id": "gid://gitlab/Releases::Link/11", + "linkType": "runbook", + "name": "Runbook", + "url": "http://localhost/releases-namespace/releases-project/runbook", + }, + Object { + "directAssetUrl": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/binaries/linux-amd64", "external": true, - "id": "gid://gitlab/Releases::Link/67", - "linkType": "image", - "name": "An example image", - "url": "https://example.com/image", + "id": "gid://gitlab/Releases::Link/10", + "linkType": "other", + "name": "linux-amd64 binaries", + "url": "https://downloads.example.com/bin/gitlab-linux-amd64", }, ], "sources": Array [ Object { "format": "zip", - "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip", + "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.zip", }, Object { "format": "tar.gz", - "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz", + "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar.gz", }, Object { "format": "tar.bz2", - "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2", + "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar.bz2", }, Object { "format": "tar", - "url": "http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar", + "url": "http://localhost/releases-namespace/releases-project/-/archive/v1.1/releases-project-v1.1.tar", }, ], }, "author": Object { - "avatarUrl": "/uploads/-/system/user/avatar/1/avatar.png", - "username": "root", - "webUrl": "http://0.0.0.0:3000/root", + "avatarUrl": "https://www.gravatar.com/avatar/16f8e2050ce10180ca571c2eb19cfce2?s=80&d=identicon", + "username": "administrator", + "webUrl": "http://localhost/administrator", }, "commit": Object { - "shortId": "92e7ea2e", - "title": "Testing a change.", + "shortId": "b83d6e39", + "title": "Merge branch 'branch-merged' into 'master'", }, - "commitPath": "http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7", - "descriptionHtml": "<p data-sourcepos=\\"1:1-1:24\\" dir=\\"auto\\">This is version <strong>1.0</strong>!</p>", + "commitPath": "http://localhost/releases-namespace/releases-project/-/commit/b83d6e391c22777fca1ed3012fce84f633d7fed0", + "descriptionHtml": "<p data-sourcepos=\\"1:1-1:33\\" dir=\\"auto\\">Best. Release. <strong>Ever.</strong> <gl-emoji title=\\"rocket\\" data-name=\\"rocket\\" data-unicode-version=\\"6.0\\">🚀</gl-emoji></p>", "evidences": Array [ Object { - "collectedAt": "2020-08-21T20:15:19Z", - "filepath": "http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json", - "sha": "22bde8e8b93d870a29ddc339287a1fbb598f45d1396d", + "collectedAt": "2018-12-03T00:00:00Z", + "filepath": "http://localhost/releases-namespace/releases-project/-/releases/v1.1/evidences/1.json", + "sha": "760d6cdfb0879c3ffedec13af470e0f71cf52c6cde4d", }, ], "milestones": Array [ Object { - "description": "", - "id": "gid://gitlab/Milestone/60", + "description": "The 12.4 milestone", + "id": "gid://gitlab/Milestone/124", "issueStats": Object { - "closed": 0, - "total": 0, + "closed": 1, + "total": 4, }, "stats": undefined, "title": "12.4", "webPath": undefined, - "webUrl": "/root/release-test/-/milestones/2", + "webUrl": "/releases-namespace/releases-project/-/milestones/2", }, Object { - "description": "Milestone 12.3", - "id": "gid://gitlab/Milestone/59", + "description": "The 12.3 milestone", + "id": "gid://gitlab/Milestone/123", "issueStats": Object { - "closed": 1, - "total": 2, + "closed": 3, + "total": 5, }, "stats": undefined, "title": "12.3", "webPath": undefined, - "webUrl": "/root/release-test/-/milestones/1", + "webUrl": "/releases-namespace/releases-project/-/milestones/1", }, ], - "name": "Release 1.0", - "releasedAt": "2020-08-21T20:15:18Z", - "tagName": "v5.10", - "tagPath": "/root/release-test/-/tags/v5.10", - "upcomingRelease": false, + "name": "The first release", + "releasedAt": "2018-12-10T00:00:00Z", + "tagName": "v1.1", + "tagPath": "/releases-namespace/releases-project/-/tags/v1.1", + "upcomingRelease": true, }, ], + "paginationInfo": Object { + "endCursor": "eyJpZCI6IjEiLCJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyJ9", + "hasNextPage": false, + "hasPreviousPage": false, + "startCursor": "eyJpZCI6IjEiLCJyZWxlYXNlZF9hdCI6IjIwMTgtMTItMTAgMDA6MDA6MDAuMDAwMDAwMDAwIFVUQyJ9", + }, } `; diff --git a/spec/frontend/releases/components/app_edit_new_spec.js b/spec/frontend/releases/components/app_edit_new_spec.js index e9727801c1a..3367ca8ba3a 100644 --- a/spec/frontend/releases/components/app_edit_new_spec.js +++ b/spec/frontend/releases/components/app_edit_new_spec.js @@ -3,12 +3,15 @@ import { mount } from '@vue/test-utils'; import { merge } from 'lodash'; import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; +import { getJSONFixture } from 'helpers/fixtures'; import ReleaseEditNewApp from '~/releases/components/app_edit_new.vue'; -import { release as originalRelease, milestones as originalMilestones } from '../mock_data'; import * as commonUtils from '~/lib/utils/common_utils'; import { BACK_URL_PARAM } from '~/releases/constants'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; +const originalRelease = getJSONFixture('api/releases/release.json'); +const originalMilestones = originalRelease.milestones; + describe('Release edit/new component', () => { let wrapper; let release; diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index bcb87509cc3..9f1577c2f1e 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -2,27 +2,33 @@ import { range as rge } from 'lodash'; import Vuex from 'vuex'; import { shallowMount, createLocalVue } from '@vue/test-utils'; import waitForPromises from 'helpers/wait_for_promises'; +import { getJSONFixture } from 'helpers/fixtures'; import ReleasesApp from '~/releases/components/app_index.vue'; import createStore from '~/releases/stores'; import createListModule from '~/releases/stores/modules/list'; import api from '~/api'; -import { - pageInfoHeadersWithoutPagination, - pageInfoHeadersWithPagination, - release2 as release, - releases, -} from '../mock_data'; +import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import TablePagination from '~/vue_shared/components/pagination/table_pagination.vue'; +import ReleasesPagination from '~/releases/components/releases_pagination.vue'; + +jest.mock('~/lib/utils/common_utils', () => ({ + ...jest.requireActual('~/lib/utils/common_utils'), + getParameterByName: jest.fn().mockImplementation(paramName => { + return `${paramName}_param_value`; + }), +})); const localVue = createLocalVue(); localVue.use(Vuex); +const release = getJSONFixture('api/releases/release.json'); +const releases = [release]; + describe('Releases App ', () => { let wrapper; let fetchReleaseSpy; - const releasesPagination = rge(21).map(index => ({ + const paginatedReleases = rge(21).map(index => ({ ...convertObjectPropsToCamelCase(release, { deep: true }), tagName: `${index}.00`, })); @@ -70,9 +76,13 @@ describe('Releases App ', () => { createComponent(); }); - it('calls fetchRelease with the page parameter', () => { + it('calls fetchRelease with the page, before, and after parameters', () => { expect(fetchReleaseSpy).toHaveBeenCalledTimes(1); - expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { page: null }); + expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { + page: 'page_param_value', + before: 'before_param_value', + after: 'after_param_value', + }); }); }); @@ -91,7 +101,7 @@ describe('Releases App ', () => { expect(wrapper.contains('.js-loading')).toBe(true); expect(wrapper.contains('.js-empty-state')).toBe(false); expect(wrapper.contains('.js-success-state')).toBe(false); - expect(wrapper.contains(TablePagination)).toBe(false); + expect(wrapper.contains(ReleasesPagination)).toBe(false); }); }); @@ -108,7 +118,7 @@ describe('Releases App ', () => { expect(wrapper.contains('.js-loading')).toBe(false); expect(wrapper.contains('.js-empty-state')).toBe(false); expect(wrapper.contains('.js-success-state')).toBe(true); - expect(wrapper.contains(TablePagination)).toBe(true); + expect(wrapper.contains(ReleasesPagination)).toBe(true); }); }); @@ -116,7 +126,7 @@ describe('Releases App ', () => { beforeEach(() => { jest .spyOn(api, 'releases') - .mockResolvedValue({ data: releasesPagination, headers: pageInfoHeadersWithPagination }); + .mockResolvedValue({ data: paginatedReleases, headers: pageInfoHeadersWithPagination }); createComponent(); }); @@ -125,7 +135,7 @@ describe('Releases App ', () => { expect(wrapper.contains('.js-loading')).toBe(false); expect(wrapper.contains('.js-empty-state')).toBe(false); expect(wrapper.contains('.js-success-state')).toBe(true); - expect(wrapper.contains(TablePagination)).toBe(true); + expect(wrapper.contains(ReleasesPagination)).toBe(true); }); }); @@ -154,7 +164,7 @@ describe('Releases App ', () => { const newReleasePath = 'path/to/new/release'; beforeEach(() => { - createComponent({ ...defaultInitialState, newReleasePath }); + createComponent({ newReleasePath }); }); it('renders the "New release" button', () => { @@ -174,4 +184,27 @@ describe('Releases App ', () => { }); }); }); + + describe('when the back button is pressed', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + + createComponent(); + + fetchReleaseSpy.mockClear(); + + window.dispatchEvent(new PopStateEvent('popstate')); + }); + + it('calls fetchRelease with the page parameter', () => { + expect(fetchReleaseSpy).toHaveBeenCalledTimes(1); + expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { + page: 'page_param_value', + before: 'before_param_value', + after: 'after_param_value', + }); + }); + }); }); diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js index 502a1053663..181fa0150f1 100644 --- a/spec/frontend/releases/components/app_show_spec.js +++ b/spec/frontend/releases/components/app_show_spec.js @@ -1,11 +1,13 @@ import Vuex from 'vuex'; import { shallowMount } from '@vue/test-utils'; -import { GlDeprecatedSkeletonLoading as GlSkeletonLoading } from '@gitlab/ui'; +import { getJSONFixture } from 'helpers/fixtures'; import ReleaseShowApp from '~/releases/components/app_show.vue'; -import { release as originalRelease } from '../mock_data'; +import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release show component', () => { let wrapper; let release; @@ -33,7 +35,7 @@ describe('Release show component', () => { wrapper = shallowMount(ReleaseShowApp, { store }); }; - const findLoadingSkeleton = () => wrapper.find(GlSkeletonLoading); + const findLoadingSkeleton = () => wrapper.find(ReleaseSkeletonLoader); const findReleaseBlock = () => wrapper.find(ReleaseBlock); it('calls fetchRelease when the component is created', () => { diff --git a/spec/frontend/releases/components/asset_links_form_spec.js b/spec/frontend/releases/components/asset_links_form_spec.js index 582c0b32716..e5b8ed267a0 100644 --- a/spec/frontend/releases/components/asset_links_form_spec.js +++ b/spec/frontend/releases/components/asset_links_form_spec.js @@ -1,7 +1,7 @@ import Vuex from 'vuex'; import { mount, createLocalVue } from '@vue/test-utils'; +import { getJSONFixture } from 'helpers/fixtures'; import AssetLinksForm from '~/releases/components/asset_links_form.vue'; -import { release as originalRelease } from '../mock_data'; import * as commonUtils from '~/lib/utils/common_utils'; import { ENTER_KEY } from '~/lib/utils/keys'; import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; @@ -9,6 +9,8 @@ import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; const localVue = createLocalVue(); localVue.use(Vuex); +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release edit component', () => { let wrapper; let release; @@ -223,10 +225,18 @@ describe('Release edit component', () => { }); }); - it('selects the default asset type if no type was provided by the backend', () => { - const selected = wrapper.find({ ref: 'typeSelect' }).element.value; + describe('when no link type was provided by the backend', () => { + beforeEach(() => { + delete release.assets.links[0].linkType; + + factory({ release }); + }); + + it('selects the default asset type', () => { + const selected = wrapper.find({ ref: 'typeSelect' }).element.value; - expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE); + expect(selected).toBe(DEFAULT_ASSET_LINK_TYPE); + }); }); }); diff --git a/spec/frontend/releases/components/evidence_block_spec.js b/spec/frontend/releases/components/evidence_block_spec.js index ba60a79e464..b8c78f90fc2 100644 --- a/spec/frontend/releases/components/evidence_block_spec.js +++ b/spec/frontend/releases/components/evidence_block_spec.js @@ -1,11 +1,13 @@ import { mount } from '@vue/test-utils'; import { GlLink, GlIcon } from '@gitlab/ui'; +import { getJSONFixture } from 'helpers/fixtures'; import { truncateSha } from '~/lib/utils/text_utility'; -import { release as originalRelease } from '../mock_data'; import EvidenceBlock from '~/releases/components/evidence_block.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Evidence Block', () => { let wrapper; let release; @@ -35,7 +37,7 @@ describe('Evidence Block', () => { }); it('renders the title for the dowload link', () => { - expect(wrapper.find(GlLink).text()).toBe('v1.1.2-evidences-1.json'); + expect(wrapper.find(GlLink).text()).toBe(`v1.1-evidences-1.json`); }); it('renders the correct hover text for the download', () => { @@ -43,7 +45,7 @@ describe('Evidence Block', () => { }); it('renders the correct file link for download', () => { - expect(wrapper.find(GlLink).attributes().download).toBe('v1.1.2-evidences-1.json'); + expect(wrapper.find(GlLink).attributes().download).toBe(`v1.1-evidences-1.json`); }); describe('sha text', () => { diff --git a/spec/frontend/releases/components/release_block_assets_spec.js b/spec/frontend/releases/components/release_block_assets_spec.js index 3453ecbf8ab..adccd9d87ef 100644 --- a/spec/frontend/releases/components/release_block_assets_spec.js +++ b/spec/frontend/releases/components/release_block_assets_spec.js @@ -1,10 +1,12 @@ import { mount } from '@vue/test-utils'; import { GlCollapse } from '@gitlab/ui'; import { trimText } from 'helpers/text_helper'; -import { cloneDeep } from 'lodash'; +import { getJSONFixture } from 'helpers/fixtures'; +import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import ReleaseBlockAssets from '~/releases/components/release_block_assets.vue'; import { ASSET_LINK_TYPE } from '~/releases/constants'; -import { assets } from '../mock_data'; + +const { assets } = getJSONFixture('api/releases/release.json'); describe('Release block assets', () => { let wrapper; @@ -31,7 +33,7 @@ describe('Release block assets', () => { wrapper.findAll('h5').filter(h5 => h5.text() === sections[type]); beforeEach(() => { - defaultProps = { assets: cloneDeep(assets) }; + defaultProps = { assets: convertObjectPropsToCamelCase(assets, { deep: true }) }; }); describe('with default props', () => { @@ -43,7 +45,7 @@ describe('Release block assets', () => { const accordionButton = findAccordionButton(); expect(accordionButton.exists()).toBe(true); - expect(trimText(accordionButton.text())).toBe('Assets 5'); + expect(trimText(accordionButton.text())).toBe('Assets 8'); }); it('renders the accordion as expanded by default', () => { diff --git a/spec/frontend/releases/components/release_block_footer_spec.js b/spec/frontend/releases/components/release_block_footer_spec.js index bde01cc0e00..f1c0c24f8ca 100644 --- a/spec/frontend/releases/components/release_block_footer_spec.js +++ b/spec/frontend/releases/components/release_block_footer_spec.js @@ -1,11 +1,13 @@ import { mount } from '@vue/test-utils'; import { GlLink, GlIcon } from '@gitlab/ui'; import { trimText } from 'helpers/text_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import { cloneDeep } from 'lodash'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; -import { release as originalRelease } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +const originalRelease = getJSONFixture('api/releases/release.json'); + const mockFutureDate = new Date(9999, 0, 0).toISOString(); let mockIsFutureRelease = false; diff --git a/spec/frontend/releases/components/release_block_header_spec.js b/spec/frontend/releases/components/release_block_header_spec.js index 9c6cbc86d3c..f2159871395 100644 --- a/spec/frontend/releases/components/release_block_header_spec.js +++ b/spec/frontend/releases/components/release_block_header_spec.js @@ -1,11 +1,13 @@ import { shallowMount } from '@vue/test-utils'; import { merge } from 'lodash'; import { GlLink } from '@gitlab/ui'; +import { getJSONFixture } from 'helpers/fixtures'; import ReleaseBlockHeader from '~/releases/components/release_block_header.vue'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import { release as originalRelease } from '../mock_data'; import { BACK_URL_PARAM } from '~/releases/constants'; +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release block header', () => { let wrapper; let release; @@ -49,7 +51,7 @@ describe('Release block header', () => { }); it('renders the title as text', () => { - expect(findHeader().text()).toBe(release.name); + expect(findHeader().text()).toContain(release.name); expect(findHeaderLink().exists()).toBe(false); }); }); diff --git a/spec/frontend/releases/components/release_block_metadata_spec.js b/spec/frontend/releases/components/release_block_metadata_spec.js index 6f184e45600..9038553fc8e 100644 --- a/spec/frontend/releases/components/release_block_metadata_spec.js +++ b/spec/frontend/releases/components/release_block_metadata_spec.js @@ -1,10 +1,12 @@ import { mount } from '@vue/test-utils'; import { trimText } from 'helpers/text_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import { cloneDeep } from 'lodash'; import ReleaseBlockMetadata from '~/releases/components/release_block_metadata.vue'; -import { release as originalRelease } from '../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +const originalRelease = getJSONFixture('api/releases/release.json'); + const mockFutureDate = new Date(9999, 0, 0).toISOString(); let mockIsFutureRelease = false; diff --git a/spec/frontend/releases/components/release_block_milestone_info_spec.js b/spec/frontend/releases/components/release_block_milestone_info_spec.js index 0e79c45b337..45f4eaa01a9 100644 --- a/spec/frontend/releases/components/release_block_milestone_info_spec.js +++ b/spec/frontend/releases/components/release_block_milestone_info_spec.js @@ -1,11 +1,13 @@ import { mount } from '@vue/test-utils'; import { GlProgressBar, GlLink, GlBadge, GlButton } from '@gitlab/ui'; import { trimText } from 'helpers/text_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import ReleaseBlockMilestoneInfo from '~/releases/components/release_block_milestone_info.vue'; -import { milestones as originalMilestones } from '../mock_data'; import { MAX_MILESTONES_TO_DISPLAY } from '~/releases/constants'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +const { milestones: originalMilestones } = getJSONFixture('api/releases/release.json'); + describe('Release block milestone info', () => { let wrapper; let milestones; @@ -35,7 +37,7 @@ describe('Release block milestone info', () => { beforeEach(() => factory({ milestones })); it('renders the correct percentage', () => { - expect(milestoneProgressBarContainer().text()).toContain('41% complete'); + expect(milestoneProgressBarContainer().text()).toContain('44% complete'); }); it('renders a progress bar that displays the correct percentage', () => { @@ -44,14 +46,24 @@ describe('Release block milestone info', () => { expect(progressBar.exists()).toBe(true); expect(progressBar.attributes()).toEqual( expect.objectContaining({ - value: '22', - max: '54', + value: '4', + max: '9', }), ); }); it('renders a list of links to all associated milestones', () => { - expect(trimText(milestoneListContainer().text())).toContain('Milestones 13.6 • 13.5'); + // The API currently returns the milestones in a non-deterministic order, + // which causes the frontend fixture used by this test to return the + // milestones in one order locally and a different order in the CI pipeline. + // This is a bug and is tracked here: https://gitlab.com/gitlab-org/gitlab/-/issues/259012 + // When this bug is fixed this expectation should be updated to + // assert the expected order. + const containerText = trimText(milestoneListContainer().text()); + expect( + containerText.includes('Milestones 12.4 • 12.3') || + containerText.includes('Milestones 12.3 • 12.4'), + ).toBe(true); milestones.forEach((m, i) => { const milestoneLink = milestoneListContainer() @@ -65,7 +77,7 @@ describe('Release block milestone info', () => { }); it('renders the "Issues" section with a total count of issues associated to the milestone(s)', () => { - const totalIssueCount = 54; + const totalIssueCount = 9; const issuesContainerText = trimText(issuesContainer().text()); expect(issuesContainerText).toContain(`Issues ${totalIssueCount}`); @@ -73,7 +85,7 @@ describe('Release block milestone info', () => { const badge = issuesContainer().find(GlBadge); expect(badge.text()).toBe(totalIssueCount.toString()); - expect(issuesContainerText).toContain('Open: 32 • Closed: 22'); + expect(issuesContainerText).toContain('Open: 5 • Closed: 4'); }); }); diff --git a/spec/frontend/releases/components/release_block_spec.js b/spec/frontend/releases/components/release_block_spec.js index a7f1388664b..af5e538b95e 100644 --- a/spec/frontend/releases/components/release_block_spec.js +++ b/spec/frontend/releases/components/release_block_spec.js @@ -1,15 +1,17 @@ import $ from 'jquery'; import { mount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; +import { getJSONFixture } from 'helpers/fixtures'; import EvidenceBlock from '~/releases/components/evidence_block.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import ReleaseBlockFooter from '~/releases/components/release_block_footer.vue'; import timeagoMixin from '~/vue_shared/mixins/timeago'; -import { release as originalRelease } from '../mock_data'; import * as commonUtils from '~/lib/utils/common_utils'; import { BACK_URL_PARAM } from '~/releases/constants'; import * as urlUtility from '~/lib/utils/url_utility'; +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release block', () => { let wrapper; let release; @@ -46,7 +48,7 @@ describe('Release block', () => { beforeEach(() => factory(release)); it("renders the block with an id equal to the release's tag name", () => { - expect(wrapper.attributes().id).toBe('v0.3'); + expect(wrapper.attributes().id).toBe(release.tagName); }); it(`renders an edit button that links to the "Edit release" page with a "${BACK_URL_PARAM}" parameter`, () => { @@ -107,7 +109,7 @@ describe('Release block', () => { }); it('does not render external label when link is not external', () => { - expect(wrapper.find('.js-assets-list li:nth-child(2) a').text()).not.toContain( + expect(wrapper.find('.js-assets-list li:nth-child(3) a').text()).not.toContain( 'external source', ); }); diff --git a/spec/frontend/releases/components/release_skeleton_loader_spec.js b/spec/frontend/releases/components/release_skeleton_loader_spec.js new file mode 100644 index 00000000000..7fbf864568a --- /dev/null +++ b/spec/frontend/releases/components/release_skeleton_loader_spec.js @@ -0,0 +1,15 @@ +import { mount } from '@vue/test-utils'; +import { GlSkeletonLoader } from '@gitlab/ui'; +import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue'; + +describe('release_skeleton_loader.vue', () => { + let wrapper; + + beforeEach(() => { + wrapper = mount(ReleaseSkeletonLoader); + }); + + it('renders a GlSkeletonLoader', () => { + expect(wrapper.find(GlSkeletonLoader).exists()).toBe(true); + }); +}); diff --git a/spec/frontend/releases/components/releases_pagination_graphql_spec.js b/spec/frontend/releases/components/releases_pagination_graphql_spec.js index b01a28eb6c3..bba5e532e5e 100644 --- a/spec/frontend/releases/components/releases_pagination_graphql_spec.js +++ b/spec/frontend/releases/components/releases_pagination_graphql_spec.js @@ -29,7 +29,7 @@ describe('~/releases/components/releases_pagination_graphql.vue', () => { listModule.state.graphQlPageInfo = pageInfo; - listModule.actions.fetchReleasesGraphQl = jest.fn(); + listModule.actions.fetchReleases = jest.fn(); wrapper = mount(ReleasesPaginationGraphql, { store: createStore({ @@ -141,8 +141,8 @@ describe('~/releases/components/releases_pagination_graphql.vue', () => { findNextButton().trigger('click'); }); - it('calls fetchReleasesGraphQl with the correct after cursor', () => { - expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ + it('calls fetchReleases with the correct after cursor', () => { + expect(listModule.actions.fetchReleases.mock.calls).toEqual([ [expect.anything(), { after: cursors.endCursor }], ]); }); @@ -159,8 +159,8 @@ describe('~/releases/components/releases_pagination_graphql.vue', () => { findPrevButton().trigger('click'); }); - it('calls fetchReleasesGraphQl with the correct before cursor', () => { - expect(listModule.actions.fetchReleasesGraphQl.mock.calls).toEqual([ + it('calls fetchReleases with the correct before cursor', () => { + expect(listModule.actions.fetchReleases.mock.calls).toEqual([ [expect.anything(), { before: cursors.startCursor }], ]); }); diff --git a/spec/frontend/releases/components/releases_pagination_rest_spec.js b/spec/frontend/releases/components/releases_pagination_rest_spec.js index 4fd3e085fc9..59c0c31413a 100644 --- a/spec/frontend/releases/components/releases_pagination_rest_spec.js +++ b/spec/frontend/releases/components/releases_pagination_rest_spec.js @@ -20,9 +20,9 @@ describe('~/releases/components/releases_pagination_rest.vue', () => { const createComponent = pageInfo => { listModule = createListModule({ projectId }); - listModule.state.pageInfo = pageInfo; + listModule.state.restPageInfo = pageInfo; - listModule.actions.fetchReleasesRest = jest.fn(); + listModule.actions.fetchReleases = jest.fn(); wrapper = mount(ReleasesPaginationRest, { store: createStore({ @@ -57,8 +57,8 @@ describe('~/releases/components/releases_pagination_rest.vue', () => { findGlPagination().vm.$emit('input', newPage); }); - it('calls fetchReleasesRest with the correct page', () => { - expect(listModule.actions.fetchReleasesRest.mock.calls).toEqual([ + it('calls fetchReleases with the correct page', () => { + expect(listModule.actions.fetchReleases.mock.calls).toEqual([ [expect.anything(), { page: newPage }], ]); }); diff --git a/spec/frontend/releases/mock_data.js b/spec/frontend/releases/mock_data.js index 58cd69a2f6a..c89182faa44 100644 --- a/spec/frontend/releases/mock_data.js +++ b/spec/frontend/releases/mock_data.js @@ -1,139 +1,3 @@ -import { ASSET_LINK_TYPE } from '~/releases/constants'; - -export const milestones = [ - { - id: 50, - iid: 2, - project_id: 18, - title: '13.6', - description: 'The 13.6 milestone!', - state: 'active', - created_at: '2019-08-27T17:22:38.280Z', - updated_at: '2019-08-27T17:22:38.280Z', - due_date: '2019-09-19', - start_date: '2019-08-31', - web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/2', - issue_stats: { - total: 33, - closed: 19, - }, - }, - { - id: 49, - iid: 1, - project_id: 18, - title: '13.5', - description: 'The 13.5 milestone!', - state: 'active', - created_at: '2019-08-26T17:55:48.643Z', - updated_at: '2019-08-26T17:55:48.643Z', - due_date: '2019-10-11', - start_date: '2019-08-19', - web_url: 'http://0.0.0.0:3001/root/release-test/-/milestones/1', - issue_stats: { - total: 21, - closed: 3, - }, - }, -]; - -export const release = { - name: 'New release', - tag_name: 'v0.3', - tag_path: '/root/release-test/-/tags/v0.3', - description: 'A super nice release!', - description_html: '<p data-sourcepos="1:1-1:21" dir="auto">A super nice release!</p>', - created_at: '2019-08-26T17:54:04.952Z', - released_at: '2019-08-26T17:54:04.807Z', - author: { - id: 1, - name: 'Administrator', - username: 'root', - state: 'active', - avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon', - web_url: 'http://0.0.0.0:3001/root', - }, - commit: { - id: 'c22b0728d1b465f82898c884d32b01aa642f96c1', - short_id: 'c22b0728', - created_at: '2019-08-26T17:47:07.000Z', - parent_ids: [], - title: 'Initial commit', - message: 'Initial commit', - author_name: 'Administrator', - author_email: 'admin@example.com', - authored_date: '2019-08-26T17:47:07.000Z', - committer_name: 'Administrator', - committer_email: 'admin@example.com', - committed_date: '2019-08-26T17:47:07.000Z', - }, - commit_path: '/root/release-test/commit/c22b0728d1b465f82898c884d32b01aa642f96c1', - upcoming_release: false, - milestones, - evidences: [ - { - filepath: - 'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/1.json', - sha: 'fb3a125fd69a0e5048ebfb0ba43eb32ce4911520dd8d', - collected_at: '2018-10-19 15:43:20 +0200', - }, - { - filepath: - 'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/2.json', - sha: '6ebd17a66e6a861175735416e49cf677678029805712dd71bb805c609e2d9108', - collected_at: '2018-10-19 15:43:20 +0200', - }, - { - filepath: - 'https://20592.qa-tunnel.gitlab.info/root/test-deployments/-/releases/v1.1.2/evidences/3.json', - sha: '2f65beaf275c3cb4b4e24fb01d481cc475d69c957830833f15338384816b5cba', - collected_at: '2018-10-19 15:43:20 +0200', - }, - ], - assets: { - count: 5, - sources: [ - { - format: 'zip', - url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.zip', - }, - { - format: 'tar.gz', - url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.gz', - }, - { - format: 'tar.bz2', - url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar.bz2', - }, - { - format: 'tar', - url: 'http://0.0.0.0:3001/root/release-test/-/archive/v0.3/release-test-v0.3.tar', - }, - ], - links: [ - { - id: 1, - name: 'my link', - url: 'https://google.com', - direct_asset_url: 'https://redirected.google.com', - external: true, - }, - { - id: 2, - name: 'my second link', - url: - 'https://gitlab.com/gitlab-org/gitlab-foss/-/jobs/artifacts/v11.6.0-rc4/download?job=rspec-mysql+41%2F50', - direct_asset_url: 'https://redirected.google.com', - external: false, - }, - ], - }, - _links: { - self: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3', - edit_url: 'http://0.0.0.0:3001/root/release-test/-/releases/v0.3/edit', - }, -}; - export const pageInfoHeadersWithoutPagination = { 'X-NEXT-PAGE': '', 'X-PAGE': '1', @@ -151,202 +15,3 @@ export const pageInfoHeadersWithPagination = { 'X-TOTAL': '21', 'X-TOTAL-PAGES': '2', }; - -export const assets = { - count: 5, - sources: [ - { - format: 'zip', - url: 'https://example.gitlab.com/path/to/zip', - }, - ], - links: [ - { - linkType: ASSET_LINK_TYPE.IMAGE, - url: 'https://example.gitlab.com/path/to/image', - directAssetUrl: 'https://example.gitlab.com/path/to/image', - name: 'Example image link', - }, - { - linkType: ASSET_LINK_TYPE.PACKAGE, - url: 'https://example.gitlab.com/path/to/package', - directAssetUrl: 'https://example.gitlab.com/path/to/package', - name: 'Example package link', - }, - { - linkType: ASSET_LINK_TYPE.RUNBOOK, - url: 'https://example.gitlab.com/path/to/runbook', - directAssetUrl: 'https://example.gitlab.com/path/to/runbook', - name: 'Example runbook link', - }, - { - linkType: ASSET_LINK_TYPE.OTHER, - url: 'https://example.gitlab.com/path/to/link', - directAssetUrl: 'https://example.gitlab.com/path/to/link', - name: 'Example link', - }, - ], -}; - -export const release2 = { - name: 'Bionic Beaver', - tag_name: '18.04', - description: '## changelog\n\n* line 1\n* line2', - description_html: '<div><h2>changelog</h2><ul><li>line1</li<li>line 2</li></ul></div>', - author_name: 'Release bot', - author_email: 'release-bot@example.com', - created_at: '2012-05-28T05:00:00-07:00', - commit: { - id: '2695effb5807a22ff3d138d593fd856244e155e7', - short_id: '2695effb', - title: 'Initial commit', - created_at: '2017-07-26T11:08:53.000+02:00', - parent_ids: ['2a4b78934375d7f53875269ffd4f45fd83a84ebe'], - message: 'Initial commit', - author: { - avatar_url: 'uploads/-/system/user/avatar/johndoe/avatar.png', - id: 482476, - name: 'John Doe', - path: '/johndoe', - state: 'active', - status_tooltip_html: null, - username: 'johndoe', - web_url: 'https://gitlab.com/johndoe', - }, - authored_date: '2012-05-28T04:42:42-07:00', - committer_name: 'Jack Smith', - committer_email: 'jack@example.com', - committed_date: '2012-05-28T04:42:42-07:00', - }, - assets, -}; - -export const releases = [release, release2]; - -export const graphqlReleasesResponse = { - data: { - project: { - releases: { - count: 39, - nodes: [ - { - name: 'Release 1.0', - tagName: 'v5.10', - tagPath: '/root/release-test/-/tags/v5.10', - descriptionHtml: - '<p data-sourcepos="1:1-1:24" dir="auto">This is version <strong>1.0</strong>!</p>', - releasedAt: '2020-08-21T20:15:18Z', - upcomingRelease: false, - assets: { - count: 7, - sources: { - nodes: [ - { - format: 'zip', - url: - 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.zip', - }, - { - format: 'tar.gz', - url: - 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.gz', - }, - { - format: 'tar.bz2', - url: - 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar.bz2', - }, - { - format: 'tar', - url: - 'http://0.0.0.0:3000/root/release-test/-/archive/v5.10/release-test-v5.10.tar', - }, - ], - }, - links: { - nodes: [ - { - id: 'gid://gitlab/Releases::Link/69', - name: 'An example link', - url: 'https://example.com/link', - directAssetUrl: - 'http://0.0.0.0:3000/root/release-test/-/releases/v5.32/permanent/path/to/runbook', - linkType: 'OTHER', - external: true, - }, - { - id: 'gid://gitlab/Releases::Link/68', - name: 'An example package link', - url: 'https://example.com/package', - directAssetUrl: 'https://example.com/package', - linkType: 'PACKAGE', - external: true, - }, - { - id: 'gid://gitlab/Releases::Link/67', - name: 'An example image', - url: 'https://example.com/image', - directAssetUrl: 'https://example.com/image', - linkType: 'IMAGE', - external: true, - }, - ], - }, - }, - evidences: { - nodes: [ - { - filepath: - 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/evidences/34.json', - collectedAt: '2020-08-21T20:15:19Z', - sha: '22bde8e8b93d870a29ddc339287a1fbb598f45d1396d', - }, - ], - }, - links: { - editUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10/edit', - issuesUrl: null, - mergeRequestsUrl: null, - selfUrl: 'http://0.0.0.0:3000/root/release-test/-/releases/v5.10', - }, - commit: { - sha: '92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7', - webUrl: - 'http://0.0.0.0:3000/root/release-test/-/commit/92e7ea2ee4496fe0d00ff69830ba0564d3d1e5a7', - title: 'Testing a change.', - }, - author: { - webUrl: 'http://0.0.0.0:3000/root', - avatarUrl: '/uploads/-/system/user/avatar/1/avatar.png', - username: 'root', - }, - milestones: { - nodes: [ - { - id: 'gid://gitlab/Milestone/60', - title: '12.4', - description: '', - webPath: '/root/release-test/-/milestones/2', - stats: { - totalIssuesCount: 0, - closedIssuesCount: 0, - }, - }, - { - id: 'gid://gitlab/Milestone/59', - title: '12.3', - description: 'Milestone 12.3', - webPath: '/root/release-test/-/milestones/1', - stats: { - totalIssuesCount: 2, - closedIssuesCount: 1, - }, - }, - ], - }, - }, - ], - }, - }, - }, -}; diff --git a/spec/frontend/releases/stores/getters_spec.js b/spec/frontend/releases/stores/getters_spec.js new file mode 100644 index 00000000000..01e10567cf0 --- /dev/null +++ b/spec/frontend/releases/stores/getters_spec.js @@ -0,0 +1,22 @@ +import * as getters from '~/releases/stores/getters'; + +describe('~/releases/stores/getters.js', () => { + it.each` + graphqlReleaseData | graphqlReleasesPage | graphqlMilestoneStats | result + ${false} | ${false} | ${false} | ${false} + ${false} | ${false} | ${true} | ${false} + ${false} | ${true} | ${false} | ${false} + ${false} | ${true} | ${true} | ${false} + ${true} | ${false} | ${false} | ${false} + ${true} | ${false} | ${true} | ${false} + ${true} | ${true} | ${false} | ${false} + ${true} | ${true} | ${true} | ${true} + `( + 'returns $result with feature flag values graphqlReleaseData=$graphqlReleaseData, graphqlReleasesPage=$graphqlReleasesPage, and graphqlMilestoneStats=$graphqlMilestoneStats', + ({ result: expectedResult, ...featureFlags }) => { + const actualResult = getters.useGraphQLEndpoint({ featureFlags }); + + expect(actualResult).toBe(expectedResult); + }, + ); +}); diff --git a/spec/frontend/releases/stores/modules/detail/actions_spec.js b/spec/frontend/releases/stores/modules/detail/actions_spec.js index 1b2a705e8f4..955c761d35a 100644 --- a/spec/frontend/releases/stores/modules/detail/actions_spec.js +++ b/spec/frontend/releases/stores/modules/detail/actions_spec.js @@ -1,10 +1,10 @@ import axios from 'axios'; import MockAdapter from 'axios-mock-adapter'; import testAction from 'helpers/vuex_action_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import { cloneDeep } from 'lodash'; import * as actions from '~/releases/stores/modules/detail/actions'; import * as types from '~/releases/stores/modules/detail/mutation_types'; -import { release as originalRelease } from '../../../mock_data'; import createState from '~/releases/stores/modules/detail/state'; import { deprecatedCreateFlash as createFlash } from '~/flash'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; @@ -21,6 +21,8 @@ jest.mock('~/lib/utils/url_utility', () => ({ joinPaths: jest.requireActual('~/lib/utils/url_utility').joinPaths, })); +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release detail actions', () => { let state; let release; @@ -207,6 +209,15 @@ describe('Release detail actions', () => { }); }); + describe('updateReleaseGroupMilestones', () => { + it(`commits ${types.UPDATE_RELEASE_GROUP_MILESTONES} with the updated release group milestones`, () => { + const newReleaseGroupMilestones = ['v0.0', 'v0.1']; + return testAction(actions.updateReleaseGroupMilestones, newReleaseGroupMilestones, state, [ + { type: types.UPDATE_RELEASE_GROUP_MILESTONES, payload: newReleaseGroupMilestones }, + ]); + }); + }); + describe('addEmptyAssetLink', () => { it(`commits ${types.ADD_EMPTY_ASSET_LINK}`, () => { return testAction(actions.addEmptyAssetLink, undefined, state, [ diff --git a/spec/frontend/releases/stores/modules/detail/mutations_spec.js b/spec/frontend/releases/stores/modules/detail/mutations_spec.js index cd7c6b7d275..f3e84262754 100644 --- a/spec/frontend/releases/stores/modules/detail/mutations_spec.js +++ b/spec/frontend/releases/stores/modules/detail/mutations_spec.js @@ -1,10 +1,12 @@ +import { getJSONFixture } from 'helpers/fixtures'; import createState from '~/releases/stores/modules/detail/state'; import mutations from '~/releases/stores/modules/detail/mutations'; import * as types from '~/releases/stores/modules/detail/mutation_types'; -import { release as originalRelease } from '../../../mock_data'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { ASSET_LINK_TYPE, DEFAULT_ASSET_LINK_TYPE } from '~/releases/constants'; +const originalRelease = getJSONFixture('api/releases/release.json'); + describe('Release detail mutations', () => { let state; let release; @@ -30,6 +32,7 @@ describe('Release detail mutations', () => { name: '', description: '', milestones: [], + groupMilestones: [], assets: { links: [], }, @@ -112,6 +115,26 @@ describe('Release detail mutations', () => { }); }); + describe(`${types.UPDATE_RELEASE_MILESTONES}`, () => { + it("updates the release's milestones", () => { + state.release = release; + const newReleaseMilestones = ['v0.0', 'v0.1']; + mutations[types.UPDATE_RELEASE_MILESTONES](state, newReleaseMilestones); + + expect(state.release.milestones).toBe(newReleaseMilestones); + }); + }); + + describe(`${types.UPDATE_RELEASE_GROUP_MILESTONES}`, () => { + it("updates the release's group milestones", () => { + state.release = release; + const newReleaseGroupMilestones = ['v0.0', 'v0.1']; + mutations[types.UPDATE_RELEASE_GROUP_MILESTONES](state, newReleaseGroupMilestones); + + expect(state.release.groupMilestones).toBe(newReleaseGroupMilestones); + }); + }); + describe(`${types.REQUEST_SAVE_RELEASE}`, () => { it('set state.isUpdatingRelease to true', () => { mutations[types.REQUEST_SAVE_RELEASE](state); diff --git a/spec/frontend/releases/stores/modules/list/actions_spec.js b/spec/frontend/releases/stores/modules/list/actions_spec.js index 95e30659d6c..2068d7fee78 100644 --- a/spec/frontend/releases/stores/modules/list/actions_spec.js +++ b/spec/frontend/releases/stores/modules/list/actions_spec.js @@ -1,31 +1,42 @@ import { cloneDeep } from 'lodash'; import testAction from 'helpers/vuex_action_helper'; +import { getJSONFixture } from 'helpers/fixtures'; import { - requestReleases, fetchReleases, - receiveReleasesSuccess, + fetchReleasesGraphQl, + fetchReleasesRest, receiveReleasesError, } from '~/releases/stores/modules/list/actions'; import createState from '~/releases/stores/modules/list/state'; import * as types from '~/releases/stores/modules/list/mutation_types'; import api from '~/api'; import { gqClient, convertGraphQLResponse } from '~/releases/util'; -import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { - pageInfoHeadersWithoutPagination, - releases as originalReleases, - graphqlReleasesResponse as originalGraphqlReleasesResponse, -} from '../../../mock_data'; + normalizeHeaders, + parseIntPagination, + convertObjectPropsToCamelCase, +} from '~/lib/utils/common_utils'; +import { pageInfoHeadersWithoutPagination } from '../../../mock_data'; import allReleasesQuery from '~/releases/queries/all_releases.query.graphql'; +import { PAGE_SIZE } from '~/releases/constants'; + +const originalRelease = getJSONFixture('api/releases/release.json'); +const originalReleases = [originalRelease]; + +const originalGraphqlReleasesResponse = getJSONFixture( + 'graphql/releases/queries/all_releases.query.graphql.json', +); describe('Releases State actions', () => { let mockedState; - let pageInfo; let releases; let graphqlReleasesResponse; const projectPath = 'root/test-project'; const projectId = 19; + const before = 'testBeforeCursor'; + const after = 'testAfterCursor'; + const page = 2; beforeEach(() => { mockedState = { @@ -33,178 +44,261 @@ describe('Releases State actions', () => { projectId, projectPath, }), - featureFlags: { - graphqlReleaseData: true, - graphqlReleasesPage: true, - graphqlMilestoneStats: true, - }, }; - pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); releases = convertObjectPropsToCamelCase(originalReleases, { deep: true }); graphqlReleasesResponse = cloneDeep(originalGraphqlReleasesResponse); }); - describe('requestReleases', () => { - it('should commit REQUEST_RELEASES mutation', done => { - testAction(requestReleases, null, mockedState, [{ type: types.REQUEST_RELEASES }], [], done); + describe('when all the necessary GraphQL feature flags are enabled', () => { + beforeEach(() => { + mockedState.useGraphQLEndpoint = true; + }); + + describe('fetchReleases', () => { + it('dispatches fetchReleasesGraphQl with before and after parameters', () => { + return testAction( + fetchReleases, + { before, after, page }, + mockedState, + [], + [ + { + type: 'fetchReleasesGraphQl', + payload: { before, after }, + }, + ], + ); + }); }); }); - describe('fetchReleases', () => { - describe('success', () => { - it('dispatches requestReleases and receiveReleasesSuccess', done => { - jest.spyOn(gqClient, 'query').mockImplementation(({ query, variables }) => { - expect(query).toBe(allReleasesQuery); - expect(variables).toEqual({ - fullPath: projectPath, + describe('when at least one of the GraphQL feature flags is disabled', () => { + beforeEach(() => { + mockedState.useGraphQLEndpoint = false; + }); + + describe('fetchReleases', () => { + it('dispatches fetchReleasesRest with a page parameter', () => { + return testAction( + fetchReleases, + { before, after, page }, + mockedState, + [], + [ + { + type: 'fetchReleasesRest', + payload: { page }, + }, + ], + ); + }); + }); + }); + + describe('fetchReleasesGraphQl', () => { + describe('GraphQL query variables', () => { + let vuexParams; + + beforeEach(() => { + jest.spyOn(gqClient, 'query'); + + vuexParams = { dispatch: jest.fn(), commit: jest.fn(), state: mockedState }; + }); + + describe('when neither a before nor an after parameter is provided', () => { + beforeEach(() => { + fetchReleasesGraphQl(vuexParams, { before: undefined, after: undefined }); + }); + + it('makes a GraphQl query with a first variable', () => { + expect(gqClient.query).toHaveBeenCalledWith({ + query: allReleasesQuery, + variables: { fullPath: projectPath, first: PAGE_SIZE }, }); - return Promise.resolve(graphqlReleasesResponse); }); + }); - testAction( - fetchReleases, + describe('when only a before parameter is provided', () => { + beforeEach(() => { + fetchReleasesGraphQl(vuexParams, { before, after: undefined }); + }); + + it('makes a GraphQl query with last and before variables', () => { + expect(gqClient.query).toHaveBeenCalledWith({ + query: allReleasesQuery, + variables: { fullPath: projectPath, last: PAGE_SIZE, before }, + }); + }); + }); + + describe('when only an after parameter is provided', () => { + beforeEach(() => { + fetchReleasesGraphQl(vuexParams, { before: undefined, after }); + }); + + it('makes a GraphQl query with first and after variables', () => { + expect(gqClient.query).toHaveBeenCalledWith({ + query: allReleasesQuery, + variables: { fullPath: projectPath, first: PAGE_SIZE, after }, + }); + }); + }); + + describe('when both before and after parameters are provided', () => { + it('throws an error', () => { + const callFetchReleasesGraphQl = () => { + fetchReleasesGraphQl(vuexParams, { before, after }); + }; + + expect(callFetchReleasesGraphQl).toThrowError( + 'Both a `before` and an `after` parameter were provided to fetchReleasesGraphQl. These parameters cannot be used together.', + ); + }); + }); + }); + + describe('when the request is successful', () => { + beforeEach(() => { + jest.spyOn(gqClient, 'query').mockResolvedValue(graphqlReleasesResponse); + }); + + it(`commits ${types.REQUEST_RELEASES} and ${types.RECEIVE_RELEASES_SUCCESS}`, () => { + const convertedResponse = convertGraphQLResponse(graphqlReleasesResponse); + + return testAction( + fetchReleasesGraphQl, {}, mockedState, - [], [ { - type: 'requestReleases', + type: types.REQUEST_RELEASES, }, { - payload: convertGraphQLResponse(graphqlReleasesResponse), - type: 'receiveReleasesSuccess', + type: types.RECEIVE_RELEASES_SUCCESS, + payload: { + data: convertedResponse.data, + graphQlPageInfo: convertedResponse.paginationInfo, + }, }, ], - done, + [], ); }); }); - describe('error', () => { - it('dispatches requestReleases and receiveReleasesError', done => { - jest.spyOn(gqClient, 'query').mockRejectedValue(); + describe('when the request fails', () => { + beforeEach(() => { + jest.spyOn(gqClient, 'query').mockRejectedValue(new Error('Something went wrong!')); + }); - testAction( - fetchReleases, + it(`commits ${types.REQUEST_RELEASES} and dispatch receiveReleasesError`, () => { + return testAction( + fetchReleasesGraphQl, {}, mockedState, - [], [ { - type: 'requestReleases', + type: types.REQUEST_RELEASES, }, + ], + [ { type: 'receiveReleasesError', }, ], - done, ); }); }); + }); + + describe('fetchReleasesRest', () => { + describe('REST query parameters', () => { + let vuexParams; - describe('when the graphqlReleaseData feature flag is disabled', () => { beforeEach(() => { - mockedState.featureFlags.graphqlReleasesPage = false; - }); + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); - describe('success', () => { - it('dispatches requestReleases and receiveReleasesSuccess', done => { - jest.spyOn(api, 'releases').mockImplementation((id, options) => { - expect(id).toBe(projectId); - expect(options.page).toBe('1'); - return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); - }); + vuexParams = { dispatch: jest.fn(), commit: jest.fn(), state: mockedState }; + }); - testAction( - fetchReleases, - {}, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, - type: 'receiveReleasesSuccess', - }, - ], - done, - ); + describe('when a page parameter is provided', () => { + beforeEach(() => { + fetchReleasesRest(vuexParams, { page: 2 }); }); - it('dispatches requestReleases and receiveReleasesSuccess on page two', done => { - jest.spyOn(api, 'releases').mockImplementation((_, options) => { - expect(options.page).toBe('2'); - return Promise.resolve({ data: releases, headers: pageInfoHeadersWithoutPagination }); - }); - - testAction( - fetchReleases, - { page: '2' }, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - payload: { data: releases, headers: pageInfoHeadersWithoutPagination }, - type: 'receiveReleasesSuccess', - }, - ], - done, - ); + it('makes a REST query with a page query parameter', () => { + expect(api.releases).toHaveBeenCalledWith(projectId, { page }); }); }); + }); - describe('error', () => { - it('dispatches requestReleases and receiveReleasesError', done => { - jest.spyOn(api, 'releases').mockReturnValue(Promise.reject()); + describe('when the request is successful', () => { + beforeEach(() => { + jest + .spyOn(api, 'releases') + .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + }); - testAction( - fetchReleases, - {}, - mockedState, - [], - [ - { - type: 'requestReleases', - }, - { - type: 'receiveReleasesError', + it(`commits ${types.REQUEST_RELEASES} and ${types.RECEIVE_RELEASES_SUCCESS}`, () => { + return testAction( + fetchReleasesRest, + {}, + mockedState, + [ + { + type: types.REQUEST_RELEASES, + }, + { + type: types.RECEIVE_RELEASES_SUCCESS, + payload: { + data: convertObjectPropsToCamelCase(releases, { deep: true }), + restPageInfo: parseIntPagination( + normalizeHeaders(pageInfoHeadersWithoutPagination), + ), }, - ], - done, - ); - }); + }, + ], + [], + ); }); }); - }); - describe('receiveReleasesSuccess', () => { - it('should commit RECEIVE_RELEASES_SUCCESS mutation', done => { - testAction( - receiveReleasesSuccess, - { data: releases, headers: pageInfoHeadersWithoutPagination }, - mockedState, - [{ type: types.RECEIVE_RELEASES_SUCCESS, payload: { pageInfo, data: releases } }], - [], - done, - ); + describe('when the request fails', () => { + beforeEach(() => { + jest.spyOn(api, 'releases').mockRejectedValue(new Error('Something went wrong!')); + }); + + it(`commits ${types.REQUEST_RELEASES} and dispatch receiveReleasesError`, () => { + return testAction( + fetchReleasesRest, + {}, + mockedState, + [ + { + type: types.REQUEST_RELEASES, + }, + ], + [ + { + type: 'receiveReleasesError', + }, + ], + ); + }); }); }); describe('receiveReleasesError', () => { - it('should commit RECEIVE_RELEASES_ERROR mutation', done => { - testAction( + it('should commit RECEIVE_RELEASES_ERROR mutation', () => { + return testAction( receiveReleasesError, null, mockedState, [{ type: types.RECEIVE_RELEASES_ERROR }], [], - done, ); }); }); diff --git a/spec/frontend/releases/stores/modules/list/mutations_spec.js b/spec/frontend/releases/stores/modules/list/mutations_spec.js index 27ad05846e7..914f69ec194 100644 --- a/spec/frontend/releases/stores/modules/list/mutations_spec.js +++ b/spec/frontend/releases/stores/modules/list/mutations_spec.js @@ -1,16 +1,29 @@ +import { getJSONFixture } from 'helpers/fixtures'; import createState from '~/releases/stores/modules/list/state'; import mutations from '~/releases/stores/modules/list/mutations'; import * as types from '~/releases/stores/modules/list/mutation_types'; -import { parseIntPagination } from '~/lib/utils/common_utils'; -import { pageInfoHeadersWithoutPagination, releases } from '../../../mock_data'; +import { parseIntPagination, convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; +import { pageInfoHeadersWithoutPagination } from '../../../mock_data'; +import { convertGraphQLResponse } from '~/releases/util'; + +const originalRelease = getJSONFixture('api/releases/release.json'); +const originalReleases = [originalRelease]; + +const graphqlReleasesResponse = getJSONFixture( + 'graphql/releases/queries/all_releases.query.graphql.json', +); describe('Releases Store Mutations', () => { let stateCopy; - let pageInfo; + let restPageInfo; + let graphQlPageInfo; + let releases; beforeEach(() => { stateCopy = createState({}); - pageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); + restPageInfo = parseIntPagination(pageInfoHeadersWithoutPagination); + graphQlPageInfo = convertGraphQLResponse(graphqlReleasesResponse).paginationInfo; + releases = convertObjectPropsToCamelCase(originalReleases, { deep: true }); }); describe('REQUEST_RELEASES', () => { @@ -23,7 +36,11 @@ describe('Releases Store Mutations', () => { describe('RECEIVE_RELEASES_SUCCESS', () => { beforeEach(() => { - mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, { pageInfo, data: releases }); + mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, { + restPageInfo, + graphQlPageInfo, + data: releases, + }); }); it('sets is loading to false', () => { @@ -38,18 +55,29 @@ describe('Releases Store Mutations', () => { expect(stateCopy.releases).toEqual(releases); }); - it('sets pageInfo', () => { - expect(stateCopy.pageInfo).toEqual(pageInfo); + it('sets restPageInfo', () => { + expect(stateCopy.restPageInfo).toEqual(restPageInfo); + }); + + it('sets graphQlPageInfo', () => { + expect(stateCopy.graphQlPageInfo).toEqual(graphQlPageInfo); }); }); describe('RECEIVE_RELEASES_ERROR', () => { it('resets data', () => { + mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, { + restPageInfo, + graphQlPageInfo, + data: releases, + }); + mutations[types.RECEIVE_RELEASES_ERROR](stateCopy); expect(stateCopy.isLoading).toEqual(false); expect(stateCopy.releases).toEqual([]); - expect(stateCopy.pageInfo).toEqual({}); + expect(stateCopy.restPageInfo).toEqual({}); + expect(stateCopy.graphQlPageInfo).toEqual({}); }); }); }); diff --git a/spec/frontend/releases/util_spec.js b/spec/frontend/releases/util_spec.js index f40e5729188..a9d0b61695d 100644 --- a/spec/frontend/releases/util_spec.js +++ b/spec/frontend/releases/util_spec.js @@ -1,6 +1,10 @@ import { cloneDeep } from 'lodash'; +import { getJSONFixture } from 'helpers/fixtures'; import { releaseToApiJson, apiJsonToRelease, convertGraphQLResponse } from '~/releases/util'; -import { graphqlReleasesResponse as originalGraphqlReleasesResponse } from './mock_data'; + +const originalGraphqlReleasesResponse = getJSONFixture( + 'graphql/releases/queries/all_releases.query.graphql.json', +); describe('releases/util.js', () => { describe('releaseToApiJson', () => { |