diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-05-19 18:44:42 +0300 |
commit | 4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch) | |
tree | 5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/releases/components | |
parent | e570267f2f6b326480d284e0164a6464ba4081bc (diff) |
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/releases/components')
6 files changed, 352 insertions, 431 deletions
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js index 7955b079cbc..3a28020c284 100644 --- a/spec/frontend/releases/components/app_index_spec.js +++ b/spec/frontend/releases/components/app_index_spec.js @@ -1,210 +1,231 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; -import { range as rge } from 'lodash'; +import { shallowMount } from '@vue/test-utils'; +import { merge } from 'lodash'; +import Vue from 'vue'; import Vuex from 'vuex'; -import { getJSONFixture } from 'helpers/fixtures'; -import waitForPromises from 'helpers/wait_for_promises'; -import api from '~/api'; -import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; -import ReleasesApp from '~/releases/components/app_index.vue'; +import { getParameterByName } from '~/lib/utils/common_utils'; +import AppIndex from '~/releases/components/app_index.vue'; +import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue'; import ReleasesPagination from '~/releases/components/releases_pagination.vue'; -import createStore from '~/releases/stores'; -import createIndexModule from '~/releases/stores/modules/index'; -import { pageInfoHeadersWithoutPagination, pageInfoHeadersWithPagination } from '../mock_data'; +import ReleasesSort from '~/releases/components/releases_sort.vue'; jest.mock('~/lib/utils/common_utils', () => ({ ...jest.requireActual('~/lib/utils/common_utils'), - getParameterByName: jest.fn().mockImplementation((paramName) => { - return `${paramName}_param_value`; - }), + getParameterByName: jest.fn(), })); -const localVue = createLocalVue(); -localVue.use(Vuex); +Vue.use(Vuex); -const release = getJSONFixture('api/releases/release.json'); -const releases = [release]; - -describe('Releases App ', () => { +describe('app_index.vue', () => { let wrapper; - let fetchReleaseSpy; - - const paginatedReleases = rge(21).map((index) => ({ - ...convertObjectPropsToCamelCase(release, { deep: true }), - tagName: `${index}.00`, - })); - - const defaultInitialState = { - projectId: 'gitlab-ce', - projectPath: 'gitlab-org/gitlab-ce', - documentationPath: 'help/releases', - illustrationPath: 'illustration/path', + let fetchReleasesSpy; + let urlParams; + + const createComponent = (storeUpdates) => { + wrapper = shallowMount(AppIndex, { + store: new Vuex.Store({ + modules: { + index: merge( + { + namespaced: true, + actions: { + fetchReleases: fetchReleasesSpy, + }, + state: { + isLoading: true, + releases: [], + }, + }, + storeUpdates, + ), + }, + }), + }); }; - const createComponent = (stateUpdates = {}) => { - const indexModule = createIndexModule({ - ...defaultInitialState, - ...stateUpdates, + beforeEach(() => { + fetchReleasesSpy = jest.fn(); + getParameterByName.mockImplementation((paramName) => urlParams[paramName]); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + // Finders + const findLoadingIndicator = () => wrapper.find(ReleaseSkeletonLoader); + const findEmptyState = () => wrapper.find('[data-testid="empty-state"]'); + const findSuccessState = () => wrapper.find('[data-testid="success-state"]'); + const findPagination = () => wrapper.find(ReleasesPagination); + const findSortControls = () => wrapper.find(ReleasesSort); + const findNewReleaseButton = () => wrapper.find('[data-testid="new-release-button"]'); + + // Expectations + const expectLoadingIndicator = (shouldExist) => { + it(`${shouldExist ? 'renders' : 'does not render'} a loading indicator`, () => { + expect(findLoadingIndicator().exists()).toBe(shouldExist); }); + }; - fetchReleaseSpy = jest.spyOn(indexModule.actions, 'fetchReleases'); + const expectEmptyState = (shouldExist) => { + it(`${shouldExist ? 'renders' : 'does not render'} an empty state`, () => { + expect(findEmptyState().exists()).toBe(shouldExist); + }); + }; - const store = createStore({ - modules: { index: indexModule }, - featureFlags: { - graphqlReleaseData: true, - graphqlReleasesPage: false, - graphqlMilestoneStats: true, - }, + const expectSuccessState = (shouldExist) => { + it(`${shouldExist ? 'renders' : 'does not render'} the success state`, () => { + expect(findSuccessState().exists()).toBe(shouldExist); }); + }; - wrapper = shallowMount(ReleasesApp, { - store, - localVue, + const expectPagination = (shouldExist) => { + it(`${shouldExist ? 'renders' : 'does not render'} the pagination controls`, () => { + expect(findPagination().exists()).toBe(shouldExist); }); }; - afterEach(() => { - wrapper.destroy(); - }); + const expectNewReleaseButton = (shouldExist) => { + it(`${shouldExist ? 'renders' : 'does not render'} the "New release" button`, () => { + expect(findNewReleaseButton().exists()).toBe(shouldExist); + }); + }; + // Tests describe('on startup', () => { - beforeEach(() => { - jest - .spyOn(api, 'releases') - .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + it.each` + before | after + ${null} | ${null} + ${'before_param_value'} | ${null} + ${null} | ${'after_param_value'} + `( + 'calls fetchRelease with the correct parameters based on the curent query parameters: before: $before, after: $after', + ({ before, after }) => { + urlParams = { before, after }; + + createComponent(); + + expect(fetchReleasesSpy).toHaveBeenCalledTimes(1); + expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams); + }, + ); + }); + describe('when the request to fetch releases has not yet completed', () => { + beforeEach(() => { createComponent(); }); - it('calls fetchRelease with the page, before, and after parameters', () => { - expect(fetchReleaseSpy).toHaveBeenCalledTimes(1); - expect(fetchReleaseSpy).toHaveBeenCalledWith(expect.anything(), { - page: 'page_param_value', - before: 'before_param_value', - after: 'after_param_value', - }); - }); + expectLoadingIndicator(true); + expectEmptyState(false); + expectSuccessState(false); + expectPagination(false); }); - describe('while loading', () => { + describe('when the request fails', () => { beforeEach(() => { - jest - .spyOn(api, 'releases') - // Need to defer the return value here to the next stack, - // otherwise the loading state disappears before our test even starts. - .mockImplementation(() => waitForPromises().then(() => ({ data: [], headers: {} }))); - - createComponent(); + createComponent({ + state: { + isLoading: false, + hasError: true, + }, + }); }); - it('renders loading icon', () => { - expect(wrapper.find('.js-loading').exists()).toBe(true); - expect(wrapper.find('.js-empty-state').exists()).toBe(false); - expect(wrapper.find('.js-success-state').exists()).toBe(false); - expect(wrapper.find(ReleasesPagination).exists()).toBe(false); - }); + expectLoadingIndicator(false); + expectEmptyState(false); + expectSuccessState(false); + expectPagination(true); }); - describe('with successful request', () => { + describe('when the request succeeds but returns no releases', () => { beforeEach(() => { - jest - .spyOn(api, 'releases') - .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); - - createComponent(); + createComponent({ + state: { + isLoading: false, + }, + }); }); - it('renders success state', () => { - expect(wrapper.find('.js-loading').exists()).toBe(false); - expect(wrapper.find('.js-empty-state').exists()).toBe(false); - expect(wrapper.find('.js-success-state').exists()).toBe(true); - expect(wrapper.find(ReleasesPagination).exists()).toBe(true); - }); + expectLoadingIndicator(false); + expectEmptyState(true); + expectSuccessState(false); + expectPagination(true); }); - describe('with successful request and pagination', () => { + describe('when the request succeeds and includes at least one release', () => { beforeEach(() => { - jest - .spyOn(api, 'releases') - .mockResolvedValue({ data: paginatedReleases, headers: pageInfoHeadersWithPagination }); - - createComponent(); + createComponent({ + state: { + isLoading: false, + releases: [{}], + }, + }); }); - it('renders success state', () => { - expect(wrapper.find('.js-loading').exists()).toBe(false); - expect(wrapper.find('.js-empty-state').exists()).toBe(false); - expect(wrapper.find('.js-success-state').exists()).toBe(true); - expect(wrapper.find(ReleasesPagination).exists()).toBe(true); - }); + expectLoadingIndicator(false); + expectEmptyState(false); + expectSuccessState(true); + expectPagination(true); }); - describe('with empty request', () => { + describe('sorting', () => { beforeEach(() => { - jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); - createComponent(); }); - it('renders empty state', () => { - expect(wrapper.find('.js-loading').exists()).toBe(false); - expect(wrapper.find('.js-empty-state').exists()).toBe(true); - expect(wrapper.find('.js-success-state').exists()).toBe(false); + it('renders the sort controls', () => { + expect(findSortControls().exists()).toBe(true); }); - }); - describe('"New release" button', () => { - const findNewReleaseButton = () => wrapper.find('.js-new-release-btn'); + it('calls the fetchReleases store method when the sort is updated', () => { + fetchReleasesSpy.mockClear(); - beforeEach(() => { - jest.spyOn(api, 'releases').mockResolvedValue({ data: [], headers: {} }); + findSortControls().vm.$emit('sort:changed'); + + expect(fetchReleasesSpy).toHaveBeenCalledTimes(1); }); + }); - describe('when the user is allowed to create a new Release', () => { - const newReleasePath = 'path/to/new/release'; + describe('"New release" button', () => { + describe('when the user is allowed to create releases', () => { + const newReleasePath = 'path/to/new/release/page'; beforeEach(() => { - createComponent({ newReleasePath }); + createComponent({ state: { newReleasePath } }); }); - it('renders the "New release" button', () => { - expect(findNewReleaseButton().exists()).toBe(true); - }); + expectNewReleaseButton(true); - it('renders the "New release" button with the correct href', () => { + it('renders the button with the correct href', () => { expect(findNewReleaseButton().attributes('href')).toBe(newReleasePath); }); }); - describe('when the user is not allowed to create a new Release', () => { - beforeEach(() => createComponent()); - - it('does not render the "New release" button', () => { - expect(findNewReleaseButton().exists()).toBe(false); + describe('when the user is not allowed to create releases', () => { + beforeEach(() => { + createComponent(); }); + + expectNewReleaseButton(false); }); }); - describe('when the back button is pressed', () => { + describe("when the browser's back button is pressed", () => { beforeEach(() => { - jest - .spyOn(api, 'releases') - .mockResolvedValue({ data: releases, headers: pageInfoHeadersWithoutPagination }); + urlParams = { + before: 'before_param_value', + }; createComponent(); - fetchReleaseSpy.mockClear(); + fetchReleasesSpy.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', - }); + it('calls the fetchRelease store method with the parameters from the URL query', () => { + expect(fetchReleasesSpy).toHaveBeenCalledTimes(1); + expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams); }); }); }); diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js index 425cb9d0059..7ea7a6ffe94 100644 --- a/spec/frontend/releases/components/app_show_spec.js +++ b/spec/frontend/releases/components/app_show_spec.js @@ -7,12 +7,12 @@ import createFlash from '~/flash'; import ReleaseShowApp from '~/releases/components/app_show.vue'; import ReleaseBlock from '~/releases/components/release_block.vue'; import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue'; -import oneReleaseQuery from '~/releases/queries/one_release.query.graphql'; +import oneReleaseQuery from '~/releases/graphql/queries/one_release.query.graphql'; jest.mock('~/flash'); const oneReleaseQueryResponse = getJSONFixture( - 'graphql/releases/queries/one_release.query.graphql.json', + 'graphql/releases/graphql/queries/one_release.query.graphql.json', ); Vue.use(VueApollo); diff --git a/spec/frontend/releases/components/releases_pagination_graphql_spec.js b/spec/frontend/releases/components/releases_pagination_graphql_spec.js deleted file mode 100644 index 5b2dd4bc784..00000000000 --- a/spec/frontend/releases/components/releases_pagination_graphql_spec.js +++ /dev/null @@ -1,175 +0,0 @@ -import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import { historyPushState } from '~/lib/utils/common_utils'; -import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue'; -import createStore from '~/releases/stores'; -import createIndexModule from '~/releases/stores/modules/index'; - -jest.mock('~/lib/utils/common_utils', () => ({ - ...jest.requireActual('~/lib/utils/common_utils'), - historyPushState: jest.fn(), -})); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('~/releases/components/releases_pagination_graphql.vue', () => { - let wrapper; - let indexModule; - - const cursors = { - startCursor: 'startCursor', - endCursor: 'endCursor', - }; - - const projectPath = 'my/project'; - - const createComponent = (pageInfo) => { - indexModule = createIndexModule({ projectPath }); - - indexModule.state.graphQlPageInfo = pageInfo; - - indexModule.actions.fetchReleases = jest.fn(); - - wrapper = mount(ReleasesPaginationGraphql, { - store: createStore({ - modules: { - index: indexModule, - }, - featureFlags: {}, - }), - localVue, - }); - }; - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - const findPrevButton = () => wrapper.find('[data-testid="prevButton"]'); - const findNextButton = () => wrapper.find('[data-testid="nextButton"]'); - - const expectDisabledPrev = () => { - expect(findPrevButton().attributes().disabled).toBe('disabled'); - }; - const expectEnabledPrev = () => { - expect(findPrevButton().attributes().disabled).toBe(undefined); - }; - const expectDisabledNext = () => { - expect(findNextButton().attributes().disabled).toBe('disabled'); - }; - const expectEnabledNext = () => { - expect(findNextButton().attributes().disabled).toBe(undefined); - }; - - describe('when there is only one page of results', () => { - beforeEach(() => { - createComponent({ - hasPreviousPage: false, - hasNextPage: false, - }); - }); - - it('does not render anything', () => { - expect(wrapper.html()).toBe(''); - }); - }); - - describe('when there is a next page, but not a previous page', () => { - beforeEach(() => { - createComponent({ - hasPreviousPage: false, - hasNextPage: true, - }); - }); - - it('renders a disabled "Prev" button', () => { - expectDisabledPrev(); - }); - - it('renders an enabled "Next" button', () => { - expectEnabledNext(); - }); - }); - - describe('when there is a previous page, but not a next page', () => { - beforeEach(() => { - createComponent({ - hasPreviousPage: true, - hasNextPage: false, - }); - }); - - it('renders a enabled "Prev" button', () => { - expectEnabledPrev(); - }); - - it('renders an disabled "Next" button', () => { - expectDisabledNext(); - }); - }); - - describe('when there is both a previous page and a next page', () => { - beforeEach(() => { - createComponent({ - hasPreviousPage: true, - hasNextPage: true, - }); - }); - - it('renders a enabled "Prev" button', () => { - expectEnabledPrev(); - }); - - it('renders an enabled "Next" button', () => { - expectEnabledNext(); - }); - }); - - describe('button behavior', () => { - beforeEach(() => { - createComponent({ - hasPreviousPage: true, - hasNextPage: true, - ...cursors, - }); - }); - - describe('next button behavior', () => { - beforeEach(() => { - findNextButton().trigger('click'); - }); - - it('calls fetchReleases with the correct after cursor', () => { - expect(indexModule.actions.fetchReleases.mock.calls).toEqual([ - [expect.anything(), { after: cursors.endCursor }], - ]); - }); - - it('calls historyPushState with the new URL', () => { - expect(historyPushState.mock.calls).toEqual([ - [expect.stringContaining(`?after=${cursors.endCursor}`)], - ]); - }); - }); - - describe('previous button behavior', () => { - beforeEach(() => { - findPrevButton().trigger('click'); - }); - - it('calls fetchReleases with the correct before cursor', () => { - expect(indexModule.actions.fetchReleases.mock.calls).toEqual([ - [expect.anything(), { before: cursors.startCursor }], - ]); - }); - - it('calls historyPushState with the new URL', () => { - expect(historyPushState.mock.calls).toEqual([ - [expect.stringContaining(`?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 deleted file mode 100644 index 7d45176967b..00000000000 --- a/spec/frontend/releases/components/releases_pagination_rest_spec.js +++ /dev/null @@ -1,72 +0,0 @@ -import { GlPagination } from '@gitlab/ui'; -import { mount, createLocalVue } from '@vue/test-utils'; -import Vuex from 'vuex'; -import * as commonUtils from '~/lib/utils/common_utils'; -import ReleasesPaginationRest from '~/releases/components/releases_pagination_rest.vue'; -import createStore from '~/releases/stores'; -import createIndexModule from '~/releases/stores/modules/index'; - -commonUtils.historyPushState = jest.fn(); - -const localVue = createLocalVue(); -localVue.use(Vuex); - -describe('~/releases/components/releases_pagination_rest.vue', () => { - let wrapper; - let indexModule; - - const projectId = 19; - - const createComponent = (pageInfo) => { - indexModule = createIndexModule({ projectId }); - - indexModule.state.restPageInfo = pageInfo; - - indexModule.actions.fetchReleases = jest.fn(); - - wrapper = mount(ReleasesPaginationRest, { - store: createStore({ - modules: { - index: indexModule, - }, - featureFlags: {}, - }), - localVue, - }); - }; - - const findGlPagination = () => wrapper.find(GlPagination); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('when a page number is clicked', () => { - const newPage = 2; - - beforeEach(() => { - createComponent({ - perPage: 20, - page: 1, - total: 40, - totalPages: 2, - nextPage: 2, - }); - - findGlPagination().vm.$emit('input', newPage); - }); - - it('calls fetchReleases with the correct page', () => { - expect(indexModule.actions.fetchReleases.mock.calls).toEqual([ - [expect.anything(), { page: newPage }], - ]); - }); - - it('calls historyPushState with the new URL', () => { - expect(commonUtils.historyPushState.mock.calls).toEqual([ - [expect.stringContaining(`?page=${newPage}`)], - ]); - }); - }); -}); diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js index 1d47da31f38..2d08f72ad8b 100644 --- a/spec/frontend/releases/components/releases_pagination_spec.js +++ b/spec/frontend/releases/components/releases_pagination_spec.js @@ -1,23 +1,46 @@ -import { shallowMount, createLocalVue } from '@vue/test-utils'; +import { GlKeysetPagination } from '@gitlab/ui'; +import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; +import { historyPushState } from '~/lib/utils/common_utils'; import ReleasesPagination from '~/releases/components/releases_pagination.vue'; -import ReleasesPaginationGraphql from '~/releases/components/releases_pagination_graphql.vue'; -import ReleasesPaginationRest from '~/releases/components/releases_pagination_rest.vue'; +import createStore from '~/releases/stores'; +import createIndexModule from '~/releases/stores/modules/index'; + +jest.mock('~/lib/utils/common_utils', () => ({ + ...jest.requireActual('~/lib/utils/common_utils'), + historyPushState: jest.fn(), +})); const localVue = createLocalVue(); localVue.use(Vuex); describe('~/releases/components/releases_pagination.vue', () => { let wrapper; + let indexModule; - const createComponent = (useGraphQLEndpoint) => { - const store = new Vuex.Store({ - getters: { - useGraphQLEndpoint: () => useGraphQLEndpoint, - }, - }); + const cursors = { + startCursor: 'startCursor', + endCursor: 'endCursor', + }; + + const projectPath = 'my/project'; + + const createComponent = (pageInfo) => { + indexModule = createIndexModule({ projectPath }); + + indexModule.state.pageInfo = pageInfo; - wrapper = shallowMount(ReleasesPagination, { store, localVue }); + indexModule.actions.fetchReleases = jest.fn(); + + wrapper = mount(ReleasesPagination, { + store: createStore({ + modules: { + index: indexModule, + }, + featureFlags: {}, + }), + localVue, + }); }; afterEach(() => { @@ -25,28 +48,130 @@ describe('~/releases/components/releases_pagination.vue', () => { wrapper = null; }); - const findRestPagination = () => wrapper.find(ReleasesPaginationRest); - const findGraphQlPagination = () => wrapper.find(ReleasesPaginationGraphql); + const findGlKeysetPagination = () => wrapper.findComponent(GlKeysetPagination); + const findPrevButton = () => findGlKeysetPagination().find('[data-testid="prevButton"]'); + const findNextButton = () => findGlKeysetPagination().find('[data-testid="nextButton"]'); + + const expectDisabledPrev = () => { + expect(findPrevButton().attributes().disabled).toBe('disabled'); + }; + const expectEnabledPrev = () => { + expect(findPrevButton().attributes().disabled).toBe(undefined); + }; + const expectDisabledNext = () => { + expect(findNextButton().attributes().disabled).toBe('disabled'); + }; + const expectEnabledNext = () => { + expect(findNextButton().attributes().disabled).toBe(undefined); + }; + + describe('when there is only one page of results', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: false, + hasNextPage: false, + }); + }); - describe('when one of necessary feature flags is disabled', () => { + it('does not render a GlKeysetPagination', () => { + expect(findGlKeysetPagination().exists()).toBe(false); + }); + }); + + describe('when there is a next page, but not a previous page', () => { beforeEach(() => { - createComponent(false); + createComponent({ + hasPreviousPage: false, + hasNextPage: true, + }); }); - it('renders the REST pagination component', () => { - expect(findRestPagination().exists()).toBe(true); - expect(findGraphQlPagination().exists()).toBe(false); + it('renders a disabled "Prev" button', () => { + expectDisabledPrev(); + }); + + it('renders an enabled "Next" button', () => { + expectEnabledNext(); }); }); - describe('when all the necessary feature flags are enabled', () => { + describe('when there is a previous page, but not a next page', () => { beforeEach(() => { - createComponent(true); + createComponent({ + hasPreviousPage: true, + hasNextPage: false, + }); }); - it('renders the GraphQL pagination component', () => { - expect(findGraphQlPagination().exists()).toBe(true); - expect(findRestPagination().exists()).toBe(false); + it('renders a enabled "Prev" button', () => { + expectEnabledPrev(); + }); + + it('renders an disabled "Next" button', () => { + expectDisabledNext(); + }); + }); + + describe('when there is both a previous page and a next page', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: true, + hasNextPage: true, + }); + }); + + it('renders a enabled "Prev" button', () => { + expectEnabledPrev(); + }); + + it('renders an enabled "Next" button', () => { + expectEnabledNext(); + }); + }); + + describe('button behavior', () => { + beforeEach(() => { + createComponent({ + hasPreviousPage: true, + hasNextPage: true, + ...cursors, + }); + }); + + describe('next button behavior', () => { + beforeEach(() => { + findNextButton().trigger('click'); + }); + + it('calls fetchReleases with the correct after cursor', () => { + expect(indexModule.actions.fetchReleases.mock.calls).toEqual([ + [expect.anything(), { after: cursors.endCursor }], + ]); + }); + + it('calls historyPushState with the new URL', () => { + expect(historyPushState.mock.calls).toEqual([ + [expect.stringContaining(`?after=${cursors.endCursor}`)], + ]); + }); + }); + + describe('previous button behavior', () => { + beforeEach(() => { + findPrevButton().trigger('click'); + }); + + it('calls fetchReleases with the correct before cursor', () => { + expect(indexModule.actions.fetchReleases.mock.calls).toEqual([ + [expect.anything(), { before: cursors.startCursor }], + ]); + }); + + it('calls historyPushState with the new URL', () => { + expect(historyPushState.mock.calls).toEqual([ + [expect.stringContaining(`?before=${cursors.startCursor}`)], + ]); + }); }); }); }); diff --git a/spec/frontend/releases/components/tag_field_new_spec.js b/spec/frontend/releases/components/tag_field_new_spec.js index f1608ca31b4..114e46ce64b 100644 --- a/spec/frontend/releases/components/tag_field_new_spec.js +++ b/spec/frontend/releases/components/tag_field_new_spec.js @@ -10,29 +10,35 @@ const TEST_PROJECT_ID = '1234'; const TEST_CREATE_FROM = 'test-create-from'; const NONEXISTENT_TAG_NAME = 'nonexistent-tag'; -// A mock version of the RefSelector component that simulates -// a scenario where the users has searched for "nonexistent-tag" -// and the component has found no tags that match. -const RefSelectorStub = Vue.component('RefSelectorStub', { - data() { - return { - footerSlotProps: { - isLoading: false, - matches: { - tags: { totalCount: 0 }, - }, - query: NONEXISTENT_TAG_NAME, - }, - }; - }, - template: '<div><slot name="footer" v-bind="footerSlotProps"></slot></div>', -}); - describe('releases/components/tag_field_new', () => { let store; let wrapper; + let RefSelectorStub; + + const createComponent = ( + mountFn = shallowMount, + { searchQuery } = { searchQuery: NONEXISTENT_TAG_NAME }, + ) => { + // A mock version of the RefSelector component that just renders the + // #footer slot, so that the content inside this slot can be tested. + RefSelectorStub = Vue.component('RefSelectorStub', { + data() { + return { + footerSlotProps: { + isLoading: false, + matches: { + tags: { + totalCount: 1, + list: [{ name: TEST_TAG_NAME }], + }, + }, + query: searchQuery, + }, + }; + }, + template: '<div><slot name="footer" v-bind="footerSlotProps"></slot></div>', + }); - const createComponent = (mountFn = shallowMount) => { wrapper = mountFn(TagFieldNew, { store, stubs: { @@ -84,8 +90,6 @@ describe('releases/components/tag_field_new', () => { describe('when the user selects a new tag name', () => { beforeEach(async () => { findCreateNewTagOption().vm.$emit('click'); - - await wrapper.vm.$nextTick(); }); it("updates the store's release.tagName property", () => { @@ -102,8 +106,6 @@ describe('releases/components/tag_field_new', () => { beforeEach(async () => { findTagNameDropdown().vm.$emit('input', updatedTagName); - - await wrapper.vm.$nextTick(); }); it("updates the store's release.tagName property", () => { @@ -116,6 +118,28 @@ describe('releases/components/tag_field_new', () => { }); }); + describe('"Create tag" option', () => { + describe('when the search query exactly matches one of the search results', () => { + beforeEach(async () => { + createComponent(mount, { searchQuery: TEST_TAG_NAME }); + }); + + it('does not show the "Create tag" option', () => { + expect(findCreateNewTagOption().exists()).toBe(false); + }); + }); + + describe('when the search query does not exactly match one of the search results', () => { + beforeEach(async () => { + createComponent(mount, { searchQuery: NONEXISTENT_TAG_NAME }); + }); + + it('shows the "Create tag" option', () => { + expect(findCreateNewTagOption().exists()).toBe(true); + }); + }); + }); + describe('validation', () => { beforeEach(() => { createComponent(mount); @@ -176,8 +200,6 @@ describe('releases/components/tag_field_new', () => { const updatedCreateFrom = 'update-create-from'; findCreateFromDropdown().vm.$emit('input', updatedCreateFrom); - await wrapper.vm.$nextTick(); - expect(store.state.editNew.createFrom).toBe(updatedCreateFrom); }); }); |