summaryrefslogtreecommitdiff
path: root/spec/frontend/releases/components
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/releases/components')
-rw-r--r--spec/frontend/releases/components/app_index_apollo_client_spec.js398
-rw-r--r--spec/frontend/releases/components/app_index_spec.js483
-rw-r--r--spec/frontend/releases/components/app_show_spec.js6
-rw-r--r--spec/frontend/releases/components/releases_pagination_apollo_client_spec.js126
-rw-r--r--spec/frontend/releases/components/releases_pagination_spec.js180
-rw-r--r--spec/frontend/releases/components/releases_sort_apollo_client_spec.js103
-rw-r--r--spec/frontend/releases/components/releases_sort_spec.js122
7 files changed, 476 insertions, 942 deletions
diff --git a/spec/frontend/releases/components/app_index_apollo_client_spec.js b/spec/frontend/releases/components/app_index_apollo_client_spec.js
deleted file mode 100644
index 9881ef9bc9f..00000000000
--- a/spec/frontend/releases/components/app_index_apollo_client_spec.js
+++ /dev/null
@@ -1,398 +0,0 @@
-import { cloneDeep } from 'lodash';
-import Vue, { nextTick } from 'vue';
-import VueApollo from 'vue-apollo';
-import originalAllReleasesQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/all_releases.query.graphql.json';
-import createMockApollo from 'helpers/mock_apollo_helper';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import waitForPromises from 'helpers/wait_for_promises';
-import allReleasesQuery from 'shared_queries/releases/all_releases.query.graphql';
-import createFlash from '~/flash';
-import { historyPushState } from '~/lib/utils/common_utils';
-import ReleasesIndexApolloClientApp from '~/releases/components/app_index_apollo_client.vue';
-import ReleaseBlock from '~/releases/components/release_block.vue';
-import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
-import ReleasesEmptyState from '~/releases/components/releases_empty_state.vue';
-import ReleasesPaginationApolloClient from '~/releases/components/releases_pagination_apollo_client.vue';
-import ReleasesSortApolloClient from '~/releases/components/releases_sort_apollo_client.vue';
-import { PAGE_SIZE, CREATED_ASC, DEFAULT_SORT } from '~/releases/constants';
-
-Vue.use(VueApollo);
-
-jest.mock('~/flash');
-
-let mockQueryParams;
-jest.mock('~/lib/utils/common_utils', () => ({
- ...jest.requireActual('~/lib/utils/common_utils'),
- historyPushState: jest.fn(),
-}));
-
-jest.mock('~/lib/utils/url_utility', () => ({
- ...jest.requireActual('~/lib/utils/url_utility'),
- getParameterByName: jest
- .fn()
- .mockImplementation((parameterName) => mockQueryParams[parameterName]),
-}));
-
-describe('app_index_apollo_client.vue', () => {
- const projectPath = 'project/path';
- const newReleasePath = 'path/to/new/release/page';
- const before = 'beforeCursor';
- const after = 'afterCursor';
-
- let wrapper;
- let allReleases;
- let singleRelease;
- let noReleases;
- let queryMock;
-
- const createComponent = ({
- singleResponse = Promise.resolve(singleRelease),
- fullResponse = Promise.resolve(allReleases),
- } = {}) => {
- const apolloProvider = createMockApollo([
- [
- allReleasesQuery,
- queryMock.mockImplementation((vars) => {
- return vars.first === 1 ? singleResponse : fullResponse;
- }),
- ],
- ]);
-
- wrapper = shallowMountExtended(ReleasesIndexApolloClientApp, {
- apolloProvider,
- provide: {
- newReleasePath,
- projectPath,
- },
- });
- };
-
- beforeEach(() => {
- mockQueryParams = {};
-
- allReleases = cloneDeep(originalAllReleasesQueryResponse);
-
- singleRelease = cloneDeep(originalAllReleasesQueryResponse);
- singleRelease.data.project.releases.nodes.splice(
- 1,
- singleRelease.data.project.releases.nodes.length,
- );
-
- noReleases = cloneDeep(originalAllReleasesQueryResponse);
- noReleases.data.project.releases.nodes = [];
-
- queryMock = jest.fn();
- });
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- // Finders
- const findLoadingIndicator = () => wrapper.findComponent(ReleaseSkeletonLoader);
- const findEmptyState = () => wrapper.findComponent(ReleasesEmptyState);
- const findNewReleaseButton = () =>
- wrapper.findByText(ReleasesIndexApolloClientApp.i18n.newRelease);
- const findAllReleaseBlocks = () => wrapper.findAllComponents(ReleaseBlock);
- const findPagination = () => wrapper.findComponent(ReleasesPaginationApolloClient);
- const findSort = () => wrapper.findComponent(ReleasesSortApolloClient);
-
- // Tests
- describe('component states', () => {
- // These need to be defined as functions, since `singleRelease` and
- // `allReleases` are generated in a `beforeEach`, and therefore
- // aren't available at test definition time.
- const getInProgressResponse = () => new Promise(() => {});
- const getErrorResponse = () => Promise.reject(new Error('Oops!'));
- const getSingleRequestLoadedResponse = () => Promise.resolve(singleRelease);
- const getFullRequestLoadedResponse = () => Promise.resolve(allReleases);
- const getLoadedEmptyResponse = () => Promise.resolve(noReleases);
-
- const toDescription = (bool) => (bool ? 'does' : 'does not');
-
- describe.each`
- description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | flashMessage | releaseCount | pagination
- ${'both requests loading'} | ${getInProgressResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
- ${'both requests failed'} | ${getErrorResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${0} | ${false}
- ${'both requests loaded'} | ${getSingleRequestLoadedResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
- ${'both requests loaded with no results'} | ${getLoadedEmptyResponse} | ${getLoadedEmptyResponse} | ${false} | ${true} | ${false} | ${0} | ${false}
- ${'single request loading, full request loaded'} | ${getInProgressResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
- ${'single request loading, full request failed'} | ${getInProgressResponse} | ${getErrorResponse} | ${true} | ${false} | ${true} | ${0} | ${false}
- ${'single request loaded, full request loading'} | ${getSingleRequestLoadedResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${1} | ${false}
- ${'single request loaded, full request failed'} | ${getSingleRequestLoadedResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${1} | ${false}
- ${'single request failed, full request loading'} | ${getErrorResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
- ${'single request failed, full request loaded'} | ${getErrorResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
- ${'single request loaded with no results, full request loading'} | ${getLoadedEmptyResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
- ${'single request loading, full request loadied with no results'} | ${getInProgressResponse} | ${getLoadedEmptyResponse} | ${false} | ${true} | ${false} | ${0} | ${false}
- `(
- '$description',
- ({
- singleResponseFn,
- fullResponseFn,
- loadingIndicator,
- emptyState,
- flashMessage,
- releaseCount,
- pagination,
- }) => {
- beforeEach(() => {
- createComponent({
- singleResponse: singleResponseFn(),
- fullResponse: fullResponseFn(),
- });
- });
-
- it(`${toDescription(loadingIndicator)} render a loading indicator`, async () => {
- await waitForPromises();
- expect(findLoadingIndicator().exists()).toBe(loadingIndicator);
- });
-
- it(`${toDescription(emptyState)} render an empty state`, () => {
- expect(findEmptyState().exists()).toBe(emptyState);
- });
-
- it(`${toDescription(flashMessage)} show a flash message`, () => {
- if (flashMessage) {
- expect(createFlash).toHaveBeenCalledWith({
- message: ReleasesIndexApolloClientApp.i18n.errorMessage,
- captureError: true,
- error: expect.any(Error),
- });
- } else {
- expect(createFlash).not.toHaveBeenCalled();
- }
- });
-
- it(`renders ${releaseCount} release(s)`, () => {
- expect(findAllReleaseBlocks()).toHaveLength(releaseCount);
- });
-
- it(`${toDescription(pagination)} render the pagination controls`, () => {
- expect(findPagination().exists()).toBe(pagination);
- });
-
- it('does render the "New release" button', () => {
- expect(findNewReleaseButton().exists()).toBe(true);
- });
-
- it('does render the sort controls', () => {
- expect(findSort().exists()).toBe(true);
- });
- },
- );
- });
-
- describe('URL parameters', () => {
- describe('when the URL contains no query parameters', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('makes a request with the correct GraphQL query parameters', () => {
- expect(queryMock).toHaveBeenCalledTimes(2);
-
- expect(queryMock).toHaveBeenCalledWith({
- first: 1,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
-
- expect(queryMock).toHaveBeenCalledWith({
- first: PAGE_SIZE,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
- });
- });
-
- describe('when the URL contains a "before" query parameter', () => {
- beforeEach(() => {
- mockQueryParams = { before };
- createComponent();
- });
-
- it('makes a request with the correct GraphQL query parameters', () => {
- expect(queryMock).toHaveBeenCalledTimes(1);
-
- expect(queryMock).toHaveBeenCalledWith({
- before,
- last: PAGE_SIZE,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
- });
- });
-
- describe('when the URL contains an "after" query parameter', () => {
- beforeEach(() => {
- mockQueryParams = { after };
- createComponent();
- });
-
- it('makes a request with the correct GraphQL query parameters', () => {
- expect(queryMock).toHaveBeenCalledTimes(2);
-
- expect(queryMock).toHaveBeenCalledWith({
- after,
- first: 1,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
-
- expect(queryMock).toHaveBeenCalledWith({
- after,
- first: PAGE_SIZE,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
- });
- });
-
- describe('when the URL contains both "before" and "after" query parameters', () => {
- beforeEach(() => {
- mockQueryParams = { before, after };
- createComponent();
- });
-
- it('ignores the "before" parameter and behaves as if only the "after" parameter was provided', () => {
- expect(queryMock).toHaveBeenCalledTimes(2);
-
- expect(queryMock).toHaveBeenCalledWith({
- after,
- first: 1,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
-
- expect(queryMock).toHaveBeenCalledWith({
- after,
- first: PAGE_SIZE,
- fullPath: projectPath,
- sort: DEFAULT_SORT,
- });
- });
- });
- });
-
- describe('New release button', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it('renders the new release button with the correct href', () => {
- expect(findNewReleaseButton().attributes().href).toBe(newReleasePath);
- });
- });
-
- describe('pagination', () => {
- beforeEach(() => {
- mockQueryParams = { before };
- createComponent();
- });
-
- it('requeries the GraphQL endpoint when a pagination button is clicked', async () => {
- expect(queryMock.mock.calls).toEqual([[expect.objectContaining({ before })]]);
-
- mockQueryParams = { after };
- findPagination().vm.$emit('next', after);
-
- await nextTick();
-
- expect(queryMock.mock.calls).toEqual([
- [expect.objectContaining({ before })],
- [expect.objectContaining({ after })],
- [expect.objectContaining({ after })],
- ]);
- });
- });
-
- describe('sorting', () => {
- beforeEach(() => {
- createComponent();
- });
-
- it(`sorts by ${DEFAULT_SORT} by default`, () => {
- expect(queryMock.mock.calls).toEqual([
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- ]);
- });
-
- it('requeries the GraphQL endpoint and updates the URL when the sort is changed', async () => {
- findSort().vm.$emit('input', CREATED_ASC);
-
- await nextTick();
-
- expect(queryMock.mock.calls).toEqual([
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- [expect.objectContaining({ sort: CREATED_ASC })],
- [expect.objectContaining({ sort: CREATED_ASC })],
- ]);
-
- // URL manipulation is tested in more detail in the `describe` block below
- expect(historyPushState).toHaveBeenCalled();
- });
-
- it('does not requery the GraphQL endpoint or update the URL if the sort is updated to the same value', async () => {
- findSort().vm.$emit('input', DEFAULT_SORT);
-
- await nextTick();
-
- expect(queryMock.mock.calls).toEqual([
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- [expect.objectContaining({ sort: DEFAULT_SORT })],
- ]);
-
- expect(historyPushState).not.toHaveBeenCalled();
- });
- });
-
- describe('sorting + pagination interaction', () => {
- const nonPaginationQueryParam = 'nonPaginationQueryParam';
-
- beforeEach(() => {
- historyPushState.mockImplementation((newUrl) => {
- mockQueryParams = Object.fromEntries(new URL(newUrl).searchParams);
- });
- });
-
- describe.each`
- queryParamsBefore | paramName | paramInitialValue
- ${{ before, nonPaginationQueryParam }} | ${'before'} | ${before}
- ${{ after, nonPaginationQueryParam }} | ${'after'} | ${after}
- `(
- 'when the URL contains a "$paramName" pagination cursor',
- ({ queryParamsBefore, paramName, paramInitialValue }) => {
- beforeEach(async () => {
- mockQueryParams = queryParamsBefore;
- createComponent();
-
- findSort().vm.$emit('input', CREATED_ASC);
-
- await nextTick();
- });
-
- it(`resets the page's "${paramName}" pagination cursor when the sort is changed`, () => {
- const firstRequestVariables = queryMock.mock.calls[0][0];
- // Might be request #2 or #3, depending on the pagination direction
- const mostRecentRequestVariables =
- queryMock.mock.calls[queryMock.mock.calls.length - 1][0];
-
- expect(firstRequestVariables[paramName]).toBe(paramInitialValue);
- expect(mostRecentRequestVariables[paramName]).toBeUndefined();
- });
-
- it(`updates the URL to not include the "${paramName}" URL query parameter`, () => {
- expect(historyPushState).toHaveBeenCalledTimes(1);
-
- const updatedUrlQueryParams = Object.fromEntries(
- new URL(historyPushState.mock.calls[0][0]).searchParams,
- );
-
- expect(updatedUrlQueryParams[paramName]).toBeUndefined();
- });
- },
- );
- });
-});
diff --git a/spec/frontend/releases/components/app_index_spec.js b/spec/frontend/releases/components/app_index_spec.js
index 43e88650ae3..63ce4c8bb17 100644
--- a/spec/frontend/releases/components/app_index_spec.js
+++ b/spec/frontend/releases/components/app_index_spec.js
@@ -1,50 +1,87 @@
-import { shallowMount } from '@vue/test-utils';
-import { merge } from 'lodash';
-import Vue from 'vue';
-import Vuex from 'vuex';
-import { getParameterByName } from '~/lib/utils/url_utility';
-import AppIndex from '~/releases/components/app_index.vue';
+import { cloneDeep } from 'lodash';
+import Vue, { nextTick } from 'vue';
+import VueApollo from 'vue-apollo';
+import originalAllReleasesQueryResponse from 'test_fixtures/graphql/releases/graphql/queries/all_releases.query.graphql.json';
+import createMockApollo from 'helpers/mock_apollo_helper';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import waitForPromises from 'helpers/wait_for_promises';
+import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
+import createFlash from '~/flash';
+import { historyPushState } from '~/lib/utils/common_utils';
+import ReleasesIndexApp from '~/releases/components/app_index.vue';
+import ReleaseBlock from '~/releases/components/release_block.vue';
import ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
+import ReleasesEmptyState from '~/releases/components/releases_empty_state.vue';
import ReleasesPagination from '~/releases/components/releases_pagination.vue';
import ReleasesSort from '~/releases/components/releases_sort.vue';
+import { PAGE_SIZE, CREATED_ASC, DEFAULT_SORT } from '~/releases/constants';
+
+Vue.use(VueApollo);
+
+jest.mock('~/flash');
+
+let mockQueryParams;
+jest.mock('~/lib/utils/common_utils', () => ({
+ ...jest.requireActual('~/lib/utils/common_utils'),
+ historyPushState: jest.fn(),
+}));
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
- getParameterByName: jest.fn(),
+ getParameterByName: jest
+ .fn()
+ .mockImplementation((parameterName) => mockQueryParams[parameterName]),
}));
-Vue.use(Vuex);
-
describe('app_index.vue', () => {
+ const projectPath = 'project/path';
+ const newReleasePath = 'path/to/new/release/page';
+ const before = 'beforeCursor';
+ const after = 'afterCursor';
+
let wrapper;
- 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,
- ),
- },
- }),
+ let allReleases;
+ let singleRelease;
+ let noReleases;
+ let queryMock;
+
+ const createComponent = ({
+ singleResponse = Promise.resolve(singleRelease),
+ fullResponse = Promise.resolve(allReleases),
+ } = {}) => {
+ const apolloProvider = createMockApollo([
+ [
+ allReleasesQuery,
+ queryMock.mockImplementation((vars) => {
+ return vars.first === 1 ? singleResponse : fullResponse;
+ }),
+ ],
+ ]);
+
+ wrapper = shallowMountExtended(ReleasesIndexApp, {
+ apolloProvider,
+ provide: {
+ newReleasePath,
+ projectPath,
+ },
});
};
beforeEach(() => {
- fetchReleasesSpy = jest.fn();
- getParameterByName.mockImplementation((paramName) => urlParams[paramName]);
+ mockQueryParams = {};
+
+ allReleases = cloneDeep(originalAllReleasesQueryResponse);
+
+ singleRelease = cloneDeep(originalAllReleasesQueryResponse);
+ singleRelease.data.project.releases.nodes.splice(
+ 1,
+ singleRelease.data.project.releases.nodes.length,
+ );
+
+ noReleases = cloneDeep(originalAllReleasesQueryResponse);
+ noReleases.data.project.releases.nodes = [];
+
+ queryMock = jest.fn();
});
afterEach(() => {
@@ -52,120 +89,221 @@ describe('app_index.vue', () => {
});
// 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);
- });
- };
+ const findLoadingIndicator = () => wrapper.findComponent(ReleaseSkeletonLoader);
+ const findEmptyState = () => wrapper.findComponent(ReleasesEmptyState);
+ const findNewReleaseButton = () => wrapper.findByText(ReleasesIndexApp.i18n.newRelease);
+ const findAllReleaseBlocks = () => wrapper.findAllComponents(ReleaseBlock);
+ const findPagination = () => wrapper.findComponent(ReleasesPagination);
+ const findSort = () => wrapper.findComponent(ReleasesSort);
- const expectEmptyState = (shouldExist) => {
- it(`${shouldExist ? 'renders' : 'does not render'} an empty state`, () => {
- expect(findEmptyState().exists()).toBe(shouldExist);
- });
- };
+ // Tests
+ describe('component states', () => {
+ // These need to be defined as functions, since `singleRelease` and
+ // `allReleases` are generated in a `beforeEach`, and therefore
+ // aren't available at test definition time.
+ const getInProgressResponse = () => new Promise(() => {});
+ const getErrorResponse = () => Promise.reject(new Error('Oops!'));
+ const getSingleRequestLoadedResponse = () => Promise.resolve(singleRelease);
+ const getFullRequestLoadedResponse = () => Promise.resolve(allReleases);
+ const getLoadedEmptyResponse = () => Promise.resolve(noReleases);
+
+ const toDescription = (bool) => (bool ? 'does' : 'does not');
+
+ describe.each`
+ description | singleResponseFn | fullResponseFn | loadingIndicator | emptyState | flashMessage | releaseCount | pagination
+ ${'both requests loading'} | ${getInProgressResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
+ ${'both requests failed'} | ${getErrorResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${0} | ${false}
+ ${'both requests loaded'} | ${getSingleRequestLoadedResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
+ ${'both requests loaded with no results'} | ${getLoadedEmptyResponse} | ${getLoadedEmptyResponse} | ${false} | ${true} | ${false} | ${0} | ${false}
+ ${'single request loading, full request loaded'} | ${getInProgressResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
+ ${'single request loading, full request failed'} | ${getInProgressResponse} | ${getErrorResponse} | ${true} | ${false} | ${true} | ${0} | ${false}
+ ${'single request loaded, full request loading'} | ${getSingleRequestLoadedResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${1} | ${false}
+ ${'single request loaded, full request failed'} | ${getSingleRequestLoadedResponse} | ${getErrorResponse} | ${false} | ${false} | ${true} | ${1} | ${false}
+ ${'single request failed, full request loading'} | ${getErrorResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
+ ${'single request failed, full request loaded'} | ${getErrorResponse} | ${getFullRequestLoadedResponse} | ${false} | ${false} | ${false} | ${2} | ${true}
+ ${'single request loaded with no results, full request loading'} | ${getLoadedEmptyResponse} | ${getInProgressResponse} | ${true} | ${false} | ${false} | ${0} | ${false}
+ ${'single request loading, full request loadied with no results'} | ${getInProgressResponse} | ${getLoadedEmptyResponse} | ${false} | ${true} | ${false} | ${0} | ${false}
+ `(
+ '$description',
+ ({
+ singleResponseFn,
+ fullResponseFn,
+ loadingIndicator,
+ emptyState,
+ flashMessage,
+ releaseCount,
+ pagination,
+ }) => {
+ beforeEach(() => {
+ createComponent({
+ singleResponse: singleResponseFn(),
+ fullResponse: fullResponseFn(),
+ });
+ });
+
+ it(`${toDescription(loadingIndicator)} render a loading indicator`, async () => {
+ await waitForPromises();
+ expect(findLoadingIndicator().exists()).toBe(loadingIndicator);
+ });
+
+ it(`${toDescription(emptyState)} render an empty state`, () => {
+ expect(findEmptyState().exists()).toBe(emptyState);
+ });
+
+ it(`${toDescription(flashMessage)} show a flash message`, async () => {
+ await waitForPromises();
+ if (flashMessage) {
+ expect(createFlash).toHaveBeenCalledWith({
+ message: ReleasesIndexApp.i18n.errorMessage,
+ captureError: true,
+ error: expect.any(Error),
+ });
+ } else {
+ expect(createFlash).not.toHaveBeenCalled();
+ }
+ });
+
+ it(`renders ${releaseCount} release(s)`, () => {
+ expect(findAllReleaseBlocks()).toHaveLength(releaseCount);
+ });
+
+ it(`${toDescription(pagination)} render the pagination controls`, () => {
+ expect(findPagination().exists()).toBe(pagination);
+ });
+
+ it('does render the "New release" button', () => {
+ expect(findNewReleaseButton().exists()).toBe(true);
+ });
+
+ it('does render the sort controls', () => {
+ expect(findSort().exists()).toBe(true);
+ });
+ },
+ );
+ });
- const expectSuccessState = (shouldExist) => {
- it(`${shouldExist ? 'renders' : 'does not render'} the success state`, () => {
- expect(findSuccessState().exists()).toBe(shouldExist);
- });
- };
+ describe('URL parameters', () => {
+ describe('when the URL contains no query parameters', () => {
+ beforeEach(() => {
+ createComponent();
+ });
- const expectPagination = (shouldExist) => {
- it(`${shouldExist ? 'renders' : 'does not render'} the pagination controls`, () => {
- expect(findPagination().exists()).toBe(shouldExist);
- });
- };
+ it('makes a request with the correct GraphQL query parameters', () => {
+ expect(queryMock).toHaveBeenCalledTimes(2);
- const expectNewReleaseButton = (shouldExist) => {
- it(`${shouldExist ? 'renders' : 'does not render'} the "New release" button`, () => {
- expect(findNewReleaseButton().exists()).toBe(shouldExist);
- });
- };
+ expect(queryMock).toHaveBeenCalledWith({
+ first: 1,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
- // Tests
- describe('on startup', () => {
- 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 };
+ expect(queryMock).toHaveBeenCalledWith({
+ first: PAGE_SIZE,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
+ });
+ });
+ describe('when the URL contains a "before" query parameter', () => {
+ beforeEach(() => {
+ mockQueryParams = { before };
createComponent();
+ });
- expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
- expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams);
- },
- );
- });
+ it('makes a request with the correct GraphQL query parameters', () => {
+ expect(queryMock).toHaveBeenCalledTimes(1);
- describe('when the request to fetch releases has not yet completed', () => {
- beforeEach(() => {
- createComponent();
+ expect(queryMock).toHaveBeenCalledWith({
+ before,
+ last: PAGE_SIZE,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
+ });
});
- expectLoadingIndicator(true);
- expectEmptyState(false);
- expectSuccessState(false);
- expectPagination(false);
- });
+ describe('when the URL contains an "after" query parameter', () => {
+ beforeEach(() => {
+ mockQueryParams = { after };
+ createComponent();
+ });
- describe('when the request fails', () => {
- beforeEach(() => {
- createComponent({
- state: {
- isLoading: false,
- hasError: true,
- },
+ it('makes a request with the correct GraphQL query parameters', () => {
+ expect(queryMock).toHaveBeenCalledTimes(2);
+
+ expect(queryMock).toHaveBeenCalledWith({
+ after,
+ first: 1,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
+
+ expect(queryMock).toHaveBeenCalledWith({
+ after,
+ first: PAGE_SIZE,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
});
});
- expectLoadingIndicator(false);
- expectEmptyState(false);
- expectSuccessState(false);
- expectPagination(true);
+ describe('when the URL contains both "before" and "after" query parameters', () => {
+ beforeEach(() => {
+ mockQueryParams = { before, after };
+ createComponent();
+ });
+
+ it('ignores the "before" parameter and behaves as if only the "after" parameter was provided', () => {
+ expect(queryMock).toHaveBeenCalledTimes(2);
+
+ expect(queryMock).toHaveBeenCalledWith({
+ after,
+ first: 1,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
+
+ expect(queryMock).toHaveBeenCalledWith({
+ after,
+ first: PAGE_SIZE,
+ fullPath: projectPath,
+ sort: DEFAULT_SORT,
+ });
+ });
+ });
});
- describe('when the request succeeds but returns no releases', () => {
+ describe('New release button', () => {
beforeEach(() => {
- createComponent({
- state: {
- isLoading: false,
- },
- });
+ createComponent();
});
- expectLoadingIndicator(false);
- expectEmptyState(true);
- expectSuccessState(false);
- expectPagination(true);
+ it('renders the new release button with the correct href', () => {
+ expect(findNewReleaseButton().attributes().href).toBe(newReleasePath);
+ });
});
- describe('when the request succeeds and includes at least one release', () => {
+ describe('pagination', () => {
beforeEach(() => {
- createComponent({
- state: {
- isLoading: false,
- releases: [{}],
- },
- });
+ mockQueryParams = { before };
+ createComponent();
});
- expectLoadingIndicator(false);
- expectEmptyState(false);
- expectSuccessState(true);
- expectPagination(true);
+ it('requeries the GraphQL endpoint when a pagination button is clicked', async () => {
+ expect(queryMock.mock.calls).toEqual([[expect.objectContaining({ before })]]);
+
+ mockQueryParams = { after };
+ findPagination().vm.$emit('next', after);
+
+ await nextTick();
+
+ expect(queryMock.mock.calls).toEqual([
+ [expect.objectContaining({ before })],
+ [expect.objectContaining({ after })],
+ [expect.objectContaining({ after })],
+ ]);
+ });
});
describe('sorting', () => {
@@ -173,59 +311,88 @@ describe('app_index.vue', () => {
createComponent();
});
- it('renders the sort controls', () => {
- expect(findSortControls().exists()).toBe(true);
+ it(`sorts by ${DEFAULT_SORT} by default`, () => {
+ expect(queryMock.mock.calls).toEqual([
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ ]);
});
- it('calls the fetchReleases store method when the sort is updated', () => {
- fetchReleasesSpy.mockClear();
+ it('requeries the GraphQL endpoint and updates the URL when the sort is changed', async () => {
+ findSort().vm.$emit('input', CREATED_ASC);
+
+ await nextTick();
- findSortControls().vm.$emit('sort:changed');
+ expect(queryMock.mock.calls).toEqual([
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ [expect.objectContaining({ sort: CREATED_ASC })],
+ [expect.objectContaining({ sort: CREATED_ASC })],
+ ]);
- expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
+ // URL manipulation is tested in more detail in the `describe` block below
+ expect(historyPushState).toHaveBeenCalled();
});
- });
- describe('"New release" button', () => {
- describe('when the user is allowed to create releases', () => {
- const newReleasePath = 'path/to/new/release/page';
+ it('does not requery the GraphQL endpoint or update the URL if the sort is updated to the same value', async () => {
+ findSort().vm.$emit('input', DEFAULT_SORT);
- beforeEach(() => {
- createComponent({ state: { newReleasePath } });
- });
+ await nextTick();
- expectNewReleaseButton(true);
+ expect(queryMock.mock.calls).toEqual([
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ [expect.objectContaining({ sort: DEFAULT_SORT })],
+ ]);
- it('renders the button with the correct href', () => {
- expect(findNewReleaseButton().attributes('href')).toBe(newReleasePath);
- });
+ expect(historyPushState).not.toHaveBeenCalled();
});
+ });
- describe('when the user is not allowed to create releases', () => {
- beforeEach(() => {
- createComponent();
- });
+ describe('sorting + pagination interaction', () => {
+ const nonPaginationQueryParam = 'nonPaginationQueryParam';
- expectNewReleaseButton(false);
+ beforeEach(() => {
+ historyPushState.mockImplementation((newUrl) => {
+ mockQueryParams = Object.fromEntries(new URL(newUrl).searchParams);
+ });
});
- });
- describe("when the browser's back button is pressed", () => {
- beforeEach(() => {
- urlParams = {
- before: 'before_param_value',
- };
+ describe.each`
+ queryParamsBefore | paramName | paramInitialValue
+ ${{ before, nonPaginationQueryParam }} | ${'before'} | ${before}
+ ${{ after, nonPaginationQueryParam }} | ${'after'} | ${after}
+ `(
+ 'when the URL contains a "$paramName" pagination cursor',
+ ({ queryParamsBefore, paramName, paramInitialValue }) => {
+ beforeEach(async () => {
+ mockQueryParams = queryParamsBefore;
+ createComponent();
- createComponent();
+ findSort().vm.$emit('input', CREATED_ASC);
- fetchReleasesSpy.mockClear();
+ await nextTick();
+ });
- window.dispatchEvent(new PopStateEvent('popstate'));
- });
+ it(`resets the page's "${paramName}" pagination cursor when the sort is changed`, () => {
+ const firstRequestVariables = queryMock.mock.calls[0][0];
+ // Might be request #2 or #3, depending on the pagination direction
+ const mostRecentRequestVariables =
+ queryMock.mock.calls[queryMock.mock.calls.length - 1][0];
- it('calls the fetchRelease store method with the parameters from the URL query', () => {
- expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
- expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams);
- });
+ expect(firstRequestVariables[paramName]).toBe(paramInitialValue);
+ expect(mostRecentRequestVariables[paramName]).toBeUndefined();
+ });
+
+ it(`updates the URL to not include the "${paramName}" URL query parameter`, () => {
+ expect(historyPushState).toHaveBeenCalledTimes(1);
+
+ const updatedUrlQueryParams = Object.fromEntries(
+ new URL(historyPushState.mock.calls[0][0]).searchParams,
+ );
+
+ expect(updatedUrlQueryParams[paramName]).toBeUndefined();
+ });
+ },
+ );
});
});
diff --git a/spec/frontend/releases/components/app_show_spec.js b/spec/frontend/releases/components/app_show_spec.js
index 41c9746a363..c2ea6900d6e 100644
--- a/spec/frontend/releases/components/app_show_spec.js
+++ b/spec/frontend/releases/components/app_show_spec.js
@@ -143,6 +143,12 @@ describe('Release show component', () => {
describe('when the request succeeded, but the returned "project.release" key was null', () => {
beforeEach(async () => {
+ // As we return a release as `null`, Apollo also throws an error to the console
+ // about the missing field. We need to suppress console.error in order to check
+ // that flash message was called
+
+ // eslint-disable-next-line no-console
+ console.error = jest.fn();
const apolloProvider = createMockApollo([
[
oneReleaseQuery,
diff --git a/spec/frontend/releases/components/releases_pagination_apollo_client_spec.js b/spec/frontend/releases/components/releases_pagination_apollo_client_spec.js
deleted file mode 100644
index a538afd5d38..00000000000
--- a/spec/frontend/releases/components/releases_pagination_apollo_client_spec.js
+++ /dev/null
@@ -1,126 +0,0 @@
-import { mountExtended } from 'helpers/vue_test_utils_helper';
-import { historyPushState } from '~/lib/utils/common_utils';
-import ReleasesPaginationApolloClient from '~/releases/components/releases_pagination_apollo_client.vue';
-
-jest.mock('~/lib/utils/common_utils', () => ({
- ...jest.requireActual('~/lib/utils/common_utils'),
- historyPushState: jest.fn(),
-}));
-
-describe('releases_pagination_apollo_client.vue', () => {
- const startCursor = 'startCursor';
- const endCursor = 'endCursor';
- let wrapper;
- let onPrev;
- let onNext;
-
- const createComponent = (pageInfo) => {
- onPrev = jest.fn();
- onNext = jest.fn();
-
- wrapper = mountExtended(ReleasesPaginationApolloClient, {
- propsData: {
- pageInfo,
- },
- listeners: {
- prev: onPrev,
- next: onNext,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const singlePageInfo = {
- hasPreviousPage: false,
- hasNextPage: false,
- startCursor,
- endCursor,
- };
-
- const onlyNextPageInfo = {
- hasPreviousPage: false,
- hasNextPage: true,
- startCursor,
- endCursor,
- };
-
- const onlyPrevPageInfo = {
- hasPreviousPage: true,
- hasNextPage: false,
- startCursor,
- endCursor,
- };
-
- const prevAndNextPageInfo = {
- hasPreviousPage: true,
- hasNextPage: true,
- startCursor,
- endCursor,
- };
-
- const findPrevButton = () => wrapper.findByTestId('prevButton');
- const findNextButton = () => wrapper.findByTestId('nextButton');
-
- describe.each`
- description | pageInfo | prevEnabled | nextEnabled
- ${'when there is only one page of results'} | ${singlePageInfo} | ${false} | ${false}
- ${'when there is a next page, but not a previous page'} | ${onlyNextPageInfo} | ${false} | ${true}
- ${'when there is a previous page, but not a next page'} | ${onlyPrevPageInfo} | ${true} | ${false}
- ${'when there is both a previous and next page'} | ${prevAndNextPageInfo} | ${true} | ${true}
- `('component states', ({ description, pageInfo, prevEnabled, nextEnabled }) => {
- describe(description, () => {
- beforeEach(() => {
- createComponent(pageInfo);
- });
-
- it(`renders the "Prev" button as ${prevEnabled ? 'enabled' : 'disabled'}`, () => {
- expect(findPrevButton().attributes().disabled).toBe(prevEnabled ? undefined : 'disabled');
- });
-
- it(`renders the "Next" button as ${nextEnabled ? 'enabled' : 'disabled'}`, () => {
- expect(findNextButton().attributes().disabled).toBe(nextEnabled ? undefined : 'disabled');
- });
- });
- });
-
- describe('button behavior', () => {
- beforeEach(() => {
- createComponent(prevAndNextPageInfo);
- });
-
- describe('next button behavior', () => {
- beforeEach(() => {
- findNextButton().trigger('click');
- });
-
- it('emits an "next" event with the "after" cursor', () => {
- expect(onNext.mock.calls).toEqual([[endCursor]]);
- });
-
- it('calls historyPushState with the new URL', () => {
- expect(historyPushState.mock.calls).toEqual([
- [expect.stringContaining(`?after=${endCursor}`)],
- ]);
- });
- });
-
- describe('prev button behavior', () => {
- beforeEach(() => {
- findPrevButton().trigger('click');
- });
-
- it('emits an "prev" event with the "before" cursor', () => {
- expect(onPrev.mock.calls).toEqual([[startCursor]]);
- });
-
- it('calls historyPushState with the new URL', () => {
- expect(historyPushState.mock.calls).toEqual([
- [expect.stringContaining(`?before=${startCursor}`)],
- ]);
- });
- });
- });
-});
diff --git a/spec/frontend/releases/components/releases_pagination_spec.js b/spec/frontend/releases/components/releases_pagination_spec.js
index b8c69b0ea70..59be808c802 100644
--- a/spec/frontend/releases/components/releases_pagination_spec.js
+++ b/spec/frontend/releases/components/releases_pagination_spec.js
@@ -1,140 +1,94 @@
-import { GlKeysetPagination } from '@gitlab/ui';
-import { mount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
+import { mountExtended } from 'helpers/vue_test_utils_helper';
import { historyPushState } from '~/lib/utils/common_utils';
import ReleasesPagination from '~/releases/components/releases_pagination.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(),
}));
-Vue.use(Vuex);
-
-describe('~/releases/components/releases_pagination.vue', () => {
+describe('releases_pagination.vue', () => {
+ const startCursor = 'startCursor';
+ const endCursor = 'endCursor';
let wrapper;
- let indexModule;
-
- const cursors = {
- startCursor: 'startCursor',
- endCursor: 'endCursor',
- };
-
- const projectPath = 'my/project';
+ let onPrev;
+ let onNext;
const createComponent = (pageInfo) => {
- indexModule = createIndexModule({ projectPath });
-
- indexModule.state.pageInfo = pageInfo;
-
- indexModule.actions.fetchReleases = jest.fn();
-
- wrapper = mount(ReleasesPagination, {
- store: createStore({
- modules: {
- index: indexModule,
- },
- featureFlags: {},
- }),
+ onPrev = jest.fn();
+ onNext = jest.fn();
+
+ wrapper = mountExtended(ReleasesPagination, {
+ propsData: {
+ pageInfo,
+ },
+ listeners: {
+ prev: onPrev,
+ next: onNext,
+ },
});
};
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- 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 singlePageInfo = {
+ hasPreviousPage: false,
+ hasNextPage: false,
+ startCursor,
+ endCursor,
};
- const expectEnabledPrev = () => {
- expect(findPrevButton().attributes().disabled).toBe(undefined);
+
+ const onlyNextPageInfo = {
+ hasPreviousPage: false,
+ hasNextPage: true,
+ startCursor,
+ endCursor,
};
- const expectDisabledNext = () => {
- expect(findNextButton().attributes().disabled).toBe('disabled');
+
+ const onlyPrevPageInfo = {
+ hasPreviousPage: true,
+ hasNextPage: false,
+ startCursor,
+ endCursor,
};
- const expectEnabledNext = () => {
- expect(findNextButton().attributes().disabled).toBe(undefined);
+
+ const prevAndNextPageInfo = {
+ hasPreviousPage: true,
+ hasNextPage: true,
+ startCursor,
+ endCursor,
};
- describe('when there is only one page of results', () => {
- beforeEach(() => {
- createComponent({
- hasPreviousPage: false,
- hasNextPage: false,
+ const findPrevButton = () => wrapper.findByTestId('prevButton');
+ const findNextButton = () => wrapper.findByTestId('nextButton');
+
+ describe.each`
+ description | pageInfo | prevEnabled | nextEnabled
+ ${'when there is only one page of results'} | ${singlePageInfo} | ${false} | ${false}
+ ${'when there is a next page, but not a previous page'} | ${onlyNextPageInfo} | ${false} | ${true}
+ ${'when there is a previous page, but not a next page'} | ${onlyPrevPageInfo} | ${true} | ${false}
+ ${'when there is both a previous and next page'} | ${prevAndNextPageInfo} | ${true} | ${true}
+ `('component states', ({ description, pageInfo, prevEnabled, nextEnabled }) => {
+ describe(description, () => {
+ beforeEach(() => {
+ createComponent(pageInfo);
});
- });
-
- 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({
- hasPreviousPage: false,
- hasNextPage: true,
+ it(`renders the "Prev" button as ${prevEnabled ? 'enabled' : 'disabled'}`, () => {
+ expect(findPrevButton().attributes().disabled).toBe(prevEnabled ? undefined : 'disabled');
});
- });
-
- 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 the "Next" button as ${nextEnabled ? 'enabled' : 'disabled'}`, () => {
+ expect(findNextButton().attributes().disabled).toBe(nextEnabled ? undefined : 'disabled');
});
});
-
- it('renders a enabled "Prev" button', () => {
- expectEnabledPrev();
- });
-
- it('renders an enabled "Next" button', () => {
- expectEnabledNext();
- });
});
describe('button behavior', () => {
beforeEach(() => {
- createComponent({
- hasPreviousPage: true,
- hasNextPage: true,
- ...cursors,
- });
+ createComponent(prevAndNextPageInfo);
});
describe('next button behavior', () => {
@@ -142,33 +96,29 @@ describe('~/releases/components/releases_pagination.vue', () => {
findNextButton().trigger('click');
});
- it('calls fetchReleases with the correct after cursor', () => {
- expect(indexModule.actions.fetchReleases.mock.calls).toEqual([
- [expect.anything(), { after: cursors.endCursor }],
- ]);
+ it('emits an "next" event with the "after" cursor', () => {
+ expect(onNext.mock.calls).toEqual([[endCursor]]);
});
it('calls historyPushState with the new URL', () => {
expect(historyPushState.mock.calls).toEqual([
- [expect.stringContaining(`?after=${cursors.endCursor}`)],
+ [expect.stringContaining(`?after=${endCursor}`)],
]);
});
});
- describe('previous button behavior', () => {
+ describe('prev 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('emits an "prev" event with the "before" cursor', () => {
+ expect(onPrev.mock.calls).toEqual([[startCursor]]);
});
it('calls historyPushState with the new URL', () => {
expect(historyPushState.mock.calls).toEqual([
- [expect.stringContaining(`?before=${cursors.startCursor}`)],
+ [expect.stringContaining(`?before=${startCursor}`)],
]);
});
});
diff --git a/spec/frontend/releases/components/releases_sort_apollo_client_spec.js b/spec/frontend/releases/components/releases_sort_apollo_client_spec.js
deleted file mode 100644
index d93a932af01..00000000000
--- a/spec/frontend/releases/components/releases_sort_apollo_client_spec.js
+++ /dev/null
@@ -1,103 +0,0 @@
-import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import ReleasesSortApolloClient from '~/releases/components/releases_sort_apollo_client.vue';
-import { RELEASED_AT_ASC, RELEASED_AT_DESC, CREATED_ASC, CREATED_DESC } from '~/releases/constants';
-
-describe('releases_sort_apollo_client.vue', () => {
- let wrapper;
-
- const createComponent = (valueProp = RELEASED_AT_ASC) => {
- wrapper = shallowMountExtended(ReleasesSortApolloClient, {
- propsData: {
- value: valueProp,
- },
- stubs: {
- GlSortingItem,
- },
- });
- };
-
- afterEach(() => {
- wrapper.destroy();
- });
-
- const findSorting = () => wrapper.findComponent(GlSorting);
- const findSortingItems = () => wrapper.findAllComponents(GlSortingItem);
- const findReleasedDateItem = () =>
- findSortingItems().wrappers.find((item) => item.text() === 'Released date');
- const findCreatedDateItem = () =>
- findSortingItems().wrappers.find((item) => item.text() === 'Created date');
- const getSortingItemsInfo = () =>
- findSortingItems().wrappers.map((item) => ({
- label: item.text(),
- active: item.attributes().active === 'true',
- }));
-
- describe.each`
- valueProp | text | isAscending | items
- ${RELEASED_AT_ASC} | ${'Released date'} | ${true} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
- ${RELEASED_AT_DESC} | ${'Released date'} | ${false} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
- ${CREATED_ASC} | ${'Created date'} | ${true} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
- ${CREATED_DESC} | ${'Created date'} | ${false} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
- `('component states', ({ valueProp, text, isAscending, items }) => {
- beforeEach(() => {
- createComponent(valueProp);
- });
-
- it(`when the sort is ${valueProp}, provides the GlSorting with the props text="${text}" and isAscending=${isAscending}`, () => {
- expect(findSorting().props()).toEqual(
- expect.objectContaining({
- text,
- isAscending,
- }),
- );
- });
-
- it(`when the sort is ${valueProp}, renders the expected dropdown items`, () => {
- expect(getSortingItemsInfo()).toEqual(items);
- });
- });
-
- const clickReleasedDateItem = () => findReleasedDateItem().vm.$emit('click');
- const clickCreatedDateItem = () => findCreatedDateItem().vm.$emit('click');
- const clickSortDirectionButton = () => findSorting().vm.$emit('sortDirectionChange');
-
- const releasedAtDropdownItemDescription = 'released at dropdown item';
- const createdAtDropdownItemDescription = 'created at dropdown item';
- const sortDirectionButtonDescription = 'sort direction button';
-
- describe.each`
- initialValueProp | itemClickFn | itemToClickDescription | emittedEvent
- ${RELEASED_AT_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
- ${RELEASED_AT_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_ASC}
- ${RELEASED_AT_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_DESC}
- ${RELEASED_AT_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
- ${RELEASED_AT_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_DESC}
- ${RELEASED_AT_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_ASC}
- ${CREATED_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_ASC}
- ${CREATED_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
- ${CREATED_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_DESC}
- ${CREATED_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_DESC}
- ${CREATED_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
- ${CREATED_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_ASC}
- `('input event', ({ initialValueProp, itemClickFn, itemToClickDescription, emittedEvent }) => {
- beforeEach(() => {
- createComponent(initialValueProp);
- itemClickFn();
- });
-
- it(`emits ${
- emittedEvent || 'nothing'
- } when value prop is ${initialValueProp} and the ${itemToClickDescription} is clicked`, () => {
- expect(wrapper.emitted().input?.[0]?.[0]).toEqual(emittedEvent);
- });
- });
-
- describe('prop validation', () => {
- it('validates that the `value` prop is one of the expected sort strings', () => {
- expect(() => {
- createComponent('not a valid value');
- }).toThrow('Invalid prop: custom validator check failed');
- });
- });
-});
diff --git a/spec/frontend/releases/components/releases_sort_spec.js b/spec/frontend/releases/components/releases_sort_spec.js
index 7774532bc12..c6e1846d252 100644
--- a/spec/frontend/releases/components/releases_sort_spec.js
+++ b/spec/frontend/releases/components/releases_sort_spec.js
@@ -1,65 +1,103 @@
import { GlSorting, GlSortingItem } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
-import Vue from 'vue';
-import Vuex from 'vuex';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import ReleasesSort from '~/releases/components/releases_sort.vue';
-import createStore from '~/releases/stores';
-import createIndexModule from '~/releases/stores/modules/index';
+import { RELEASED_AT_ASC, RELEASED_AT_DESC, CREATED_ASC, CREATED_DESC } from '~/releases/constants';
-Vue.use(Vuex);
-
-describe('~/releases/components/releases_sort.vue', () => {
+describe('releases_sort.vue', () => {
let wrapper;
- let store;
- let indexModule;
- const projectId = 8;
-
- const createComponent = () => {
- indexModule = createIndexModule({ projectId });
- store = createStore({
- modules: {
- index: indexModule,
+ const createComponent = (valueProp = RELEASED_AT_ASC) => {
+ wrapper = shallowMountExtended(ReleasesSort, {
+ propsData: {
+ value: valueProp,
},
- });
-
- store.dispatch = jest.fn();
-
- wrapper = shallowMount(ReleasesSort, {
- store,
stubs: {
GlSortingItem,
},
});
};
- const findReleasesSorting = () => wrapper.find(GlSorting);
- const findSortingItems = () => wrapper.findAll(GlSortingItem);
-
afterEach(() => {
wrapper.destroy();
- wrapper = null;
});
- beforeEach(() => {
- createComponent();
- });
+ const findSorting = () => wrapper.findComponent(GlSorting);
+ const findSortingItems = () => wrapper.findAllComponents(GlSortingItem);
+ const findReleasedDateItem = () =>
+ findSortingItems().wrappers.find((item) => item.text() === 'Released date');
+ const findCreatedDateItem = () =>
+ findSortingItems().wrappers.find((item) => item.text() === 'Created date');
+ const getSortingItemsInfo = () =>
+ findSortingItems().wrappers.map((item) => ({
+ label: item.text(),
+ active: item.attributes().active === 'true',
+ }));
+
+ describe.each`
+ valueProp | text | isAscending | items
+ ${RELEASED_AT_ASC} | ${'Released date'} | ${true} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
+ ${RELEASED_AT_DESC} | ${'Released date'} | ${false} | ${[{ label: 'Released date', active: true }, { label: 'Created date', active: false }]}
+ ${CREATED_ASC} | ${'Created date'} | ${true} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
+ ${CREATED_DESC} | ${'Created date'} | ${false} | ${[{ label: 'Released date', active: false }, { label: 'Created date', active: true }]}
+ `('component states', ({ valueProp, text, isAscending, items }) => {
+ beforeEach(() => {
+ createComponent(valueProp);
+ });
- it('has all the sortable items', () => {
- expect(findSortingItems()).toHaveLength(wrapper.vm.sortOptions.length);
+ it(`when the sort is ${valueProp}, provides the GlSorting with the props text="${text}" and isAscending=${isAscending}`, () => {
+ expect(findSorting().props()).toEqual(
+ expect.objectContaining({
+ text,
+ isAscending,
+ }),
+ );
+ });
+
+ it(`when the sort is ${valueProp}, renders the expected dropdown items`, () => {
+ expect(getSortingItemsInfo()).toEqual(items);
+ });
});
- it('on sort change set sorting in vuex and emit event', () => {
- findReleasesSorting().vm.$emit('sortDirectionChange');
- expect(store.dispatch).toHaveBeenCalledWith('index/setSorting', { sort: 'asc' });
- expect(wrapper.emitted('sort:changed')).toBeTruthy();
+ const clickReleasedDateItem = () => findReleasedDateItem().vm.$emit('click');
+ const clickCreatedDateItem = () => findCreatedDateItem().vm.$emit('click');
+ const clickSortDirectionButton = () => findSorting().vm.$emit('sortDirectionChange');
+
+ const releasedAtDropdownItemDescription = 'released at dropdown item';
+ const createdAtDropdownItemDescription = 'created at dropdown item';
+ const sortDirectionButtonDescription = 'sort direction button';
+
+ describe.each`
+ initialValueProp | itemClickFn | itemToClickDescription | emittedEvent
+ ${RELEASED_AT_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
+ ${RELEASED_AT_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_ASC}
+ ${RELEASED_AT_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_DESC}
+ ${RELEASED_AT_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${undefined}
+ ${RELEASED_AT_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${CREATED_DESC}
+ ${RELEASED_AT_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${RELEASED_AT_ASC}
+ ${CREATED_ASC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_ASC}
+ ${CREATED_ASC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
+ ${CREATED_ASC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_DESC}
+ ${CREATED_DESC} | ${clickReleasedDateItem} | ${releasedAtDropdownItemDescription} | ${RELEASED_AT_DESC}
+ ${CREATED_DESC} | ${clickCreatedDateItem} | ${createdAtDropdownItemDescription} | ${undefined}
+ ${CREATED_DESC} | ${clickSortDirectionButton} | ${sortDirectionButtonDescription} | ${CREATED_ASC}
+ `('input event', ({ initialValueProp, itemClickFn, itemToClickDescription, emittedEvent }) => {
+ beforeEach(() => {
+ createComponent(initialValueProp);
+ itemClickFn();
+ });
+
+ it(`emits ${
+ emittedEvent || 'nothing'
+ } when value prop is ${initialValueProp} and the ${itemToClickDescription} is clicked`, () => {
+ expect(wrapper.emitted().input?.[0]?.[0]).toEqual(emittedEvent);
+ });
});
- it('on sort item click set sorting and emit event', () => {
- const item = findSortingItems().at(0);
- const { orderBy } = wrapper.vm.sortOptions[0];
- item.vm.$emit('click');
- expect(store.dispatch).toHaveBeenCalledWith('index/setSorting', { orderBy });
- expect(wrapper.emitted('sort:changed')).toBeTruthy();
+ describe('prop validation', () => {
+ it('validates that the `value` prop is one of the expected sort strings', () => {
+ expect(() => {
+ createComponent('not a valid value');
+ }).toThrow('Invalid prop: custom validator check failed');
+ });
});
});