summaryrefslogtreecommitdiff
path: root/spec/frontend/releases/components
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-05-19 15:44:42 +0000
commit4555e1b21c365ed8303ffb7a3325d773c9b8bf31 (patch)
tree5423a1c7516cffe36384133ade12572cf709398d /spec/frontend/releases/components
parente570267f2f6b326480d284e0164a6464ba4081bc (diff)
downloadgitlab-ce-4555e1b21c365ed8303ffb7a3325d773c9b8bf31.tar.gz
Add latest changes from gitlab-org/gitlab@13-12-stable-eev13.12.0-rc42
Diffstat (limited to 'spec/frontend/releases/components')
-rw-r--r--spec/frontend/releases/components/app_index_spec.js291
-rw-r--r--spec/frontend/releases/components/app_show_spec.js4
-rw-r--r--spec/frontend/releases/components/releases_pagination_graphql_spec.js175
-rw-r--r--spec/frontend/releases/components/releases_pagination_rest_spec.js72
-rw-r--r--spec/frontend/releases/components/releases_pagination_spec.js169
-rw-r--r--spec/frontend/releases/components/tag_field_new_spec.js72
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);
});
});