Commit 07c7ad19 authored by Andrew Fontaine's avatar Andrew Fontaine Committed by Paul Slaughter

Remove Vuex Releases Components

With the removal of the releases_index_apollo_client flag, these
Vuex-based components are no longer used. They can be removed.

The files with the apollo_client suffix are moved in a separate commit
to keep the complexity of the diff low.
parent 876cf04d
<script>
import { GlEmptyState, GlLink, GlButton } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { getParameterByName } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import ReleaseBlock from './release_block.vue';
import ReleaseSkeletonLoader from './release_skeleton_loader.vue';
import ReleasesPagination from './releases_pagination.vue';
import ReleasesSort from './releases_sort.vue';
export default {
name: 'ReleasesApp',
components: {
GlEmptyState,
GlLink,
GlButton,
ReleaseBlock,
ReleasesPagination,
ReleaseSkeletonLoader,
ReleasesSort,
},
computed: {
...mapState('index', [
'documentationPath',
'illustrationPath',
'newReleasePath',
'isLoading',
'releases',
'hasError',
]),
shouldRenderEmptyState() {
return !this.releases.length && !this.hasError && !this.isLoading;
},
shouldRenderSuccessState() {
return this.releases.length && !this.isLoading && !this.hasError;
},
emptyStateText() {
return __(
"Releases are based on Git tags and mark specific points in a project's development history. They can contain information about the type of changes and can also deliver binaries, like compiled versions of your software.",
);
},
},
created() {
this.fetchReleases();
window.addEventListener('popstate', this.fetchReleases);
},
methods: {
...mapActions('index', {
fetchReleasesStoreAction: 'fetchReleases',
}),
fetchReleases() {
this.fetchReleasesStoreAction({
before: getParameterByName('before'),
after: getParameterByName('after'),
});
},
},
};
</script>
<template>
<div class="flex flex-column mt-2">
<div class="gl-align-self-end gl-mb-3">
<releases-sort class="gl-mr-2" @sort:changed="fetchReleases" />
<gl-button
v-if="newReleasePath"
:href="newReleasePath"
:aria-describedby="shouldRenderEmptyState && 'releases-description'"
category="primary"
variant="confirm"
data-testid="new-release-button"
>
{{ __('New release') }}
</gl-button>
</div>
<release-skeleton-loader v-if="isLoading" />
<gl-empty-state
v-else-if="shouldRenderEmptyState"
data-testid="empty-state"
:title="__('Getting started with releases')"
:svg-path="illustrationPath"
>
<template #description>
<span id="releases-description">
{{ emptyStateText }}
<gl-link
:href="documentationPath"
:aria-label="__('Releases documentation')"
target="_blank"
>
{{ __('More information') }}
</gl-link>
</span>
</template>
</gl-empty-state>
<div v-else-if="shouldRenderSuccessState" data-testid="success-state">
<release-block
v-for="(release, index) in releases"
:key="index"
:release="release"
:class="{ 'linked-card': releases.length > 1 && index !== releases.length - 1 }"
/>
</div>
<releases-pagination v-if="!isLoading" />
</div>
</template>
<style>
.linked-card::after {
width: 1px;
content: ' ';
border: 1px solid #e5e5e5;
height: 17px;
top: 100%;
position: absolute;
left: 32px;
}
</style>
<script>
import { GlKeysetPagination } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { historyPushState, buildUrlWithCurrentLocation } from '~/lib/utils/common_utils';
export default {
name: 'ReleasesPaginationGraphql',
components: { GlKeysetPagination },
computed: {
...mapState('index', ['pageInfo']),
showPagination() {
return this.pageInfo.hasPreviousPage || this.pageInfo.hasNextPage;
},
},
methods: {
...mapActions('index', ['fetchReleases']),
onPrev(before) {
historyPushState(buildUrlWithCurrentLocation(`?before=${before}`));
this.fetchReleases({ before });
},
onNext(after) {
historyPushState(buildUrlWithCurrentLocation(`?after=${after}`));
this.fetchReleases({ after });
},
},
};
</script>
<template>
<div class="gl-display-flex gl-justify-content-center">
<gl-keyset-pagination
v-if="showPagination"
v-bind="pageInfo"
@prev="onPrev($event)"
@next="onNext($event)"
/>
</div>
</template>
<script>
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { mapState, mapActions } from 'vuex';
import { ASCENDING_ORDER, DESCENDING_ORDER, SORT_OPTIONS } from '../constants';
export default {
name: 'ReleasesSort',
components: {
GlSorting,
GlSortingItem,
},
computed: {
...mapState('index', {
orderBy: (state) => state.sorting.orderBy,
sort: (state) => state.sorting.sort,
}),
sortOptions() {
return SORT_OPTIONS;
},
sortText() {
const option = this.sortOptions.find((s) => s.orderBy === this.orderBy);
return option.label;
},
isSortAscending() {
return this.sort === ASCENDING_ORDER;
},
},
methods: {
...mapActions('index', ['setSorting']),
onDirectionChange() {
const sort = this.isSortAscending ? DESCENDING_ORDER : ASCENDING_ORDER;
this.setSorting({ sort });
this.$emit('sort:changed');
},
onSortItemClick(item) {
this.setSorting({ orderBy: item });
this.$emit('sort:changed');
},
isActiveSortItem(item) {
return this.orderBy === item;
},
},
};
</script>
<template>
<gl-sorting
:text="sortText"
:is-ascending="isSortAscending"
data-testid="releases-sort"
@sortDirectionChange="onDirectionChange"
>
<gl-sorting-item
v-for="item in sortOptions"
:key="item.orderBy"
:active="isActiveSortItem(item.orderBy)"
@click="onSortItemClick(item.orderBy)"
>
{{ item.label }}
</gl-sorting-item>
</gl-sorting>
</template>
#import "../fragments/release.fragment.graphql"
# This query is identical to
# `app/graphql/queries/releases/all_releases.query.graphql`.
# These two queries should be kept in sync.
# When the `releases_index_apollo_client` feature flag is
# removed, this query should be removed entirely.
query allReleasesDeprecated(
$fullPath: ID!
$first: Int
$last: Int
$before: String
$after: String
$sort: ReleaseSort
) {
project(fullPath: $fullPath) {
__typename
id
releases(first: $first, last: $last, before: $before, after: $after, sort: $sort) {
__typename
nodes {
...Release
}
pageInfo {
__typename
startCursor
hasPreviousPage
hasNextPage
endCursor
}
}
}
}
import createFlash from '~/flash';
import { __ } from '~/locale';
import { PAGE_SIZE } from '~/releases/constants';
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
import { gqClient, convertAllReleasesGraphQLResponse } from '~/releases/util';
import * as types from './mutation_types';
/**
* Gets a paginated list of releases from the GraphQL endpoint
*
* @param {Object} vuexParams
* @param {Object} actionParams
* @param {String} [actionParams.before] A GraphQL cursor. If provided,
* the items returned will proceed the provided cursor.
* @param {String} [actionParams.after] A GraphQL cursor. If provided,
* the items returned will follow the provided cursor.
*/
export const fetchReleases = ({ dispatch, commit, state }, { before, after }) => {
commit(types.REQUEST_RELEASES);
const { sort, orderBy } = state.sorting;
const orderByParam = orderBy === 'created_at' ? 'created' : orderBy;
const sortParams = `${orderByParam}_${sort}`.toUpperCase();
let paginationParams;
if (!before && !after) {
paginationParams = { first: PAGE_SIZE };
} else if (before && !after) {
paginationParams = { last: PAGE_SIZE, before };
} else if (!before && after) {
paginationParams = { first: PAGE_SIZE, after };
} else {
throw new Error(
'Both a `before` and an `after` parameter were provided to fetchReleases. These parameters cannot be used together.',
);
}
gqClient
.query({
query: allReleasesQuery,
variables: {
fullPath: state.projectPath,
sort: sortParams,
...paginationParams,
},
})
.then((response) => {
const { data, paginationInfo: pageInfo } = convertAllReleasesGraphQLResponse(response);
commit(types.RECEIVE_RELEASES_SUCCESS, {
data,
pageInfo,
});
})
.catch(() => dispatch('receiveReleasesError'));
};
export const receiveReleasesError = ({ commit }) => {
commit(types.RECEIVE_RELEASES_ERROR);
createFlash({
message: __('An error occurred while fetching the releases. Please try again.'),
});
};
export const setSorting = ({ commit }, data) => commit(types.SET_SORTING, data);
import * as actions from './actions';
import mutations from './mutations';
import createState from './state';
export default (initialState) => ({
namespaced: true,
actions,
mutations,
state: createState(initialState),
});
export const REQUEST_RELEASES = 'REQUEST_RELEASES';
export const RECEIVE_RELEASES_SUCCESS = 'RECEIVE_RELEASES_SUCCESS';
export const RECEIVE_RELEASES_ERROR = 'RECEIVE_RELEASES_ERROR';
export const SET_SORTING = 'SET_SORTING';
import * as types from './mutation_types';
export default {
/**
* Sets isLoading to true while the request is being made.
* @param {Object} state
*/
[types.REQUEST_RELEASES](state) {
state.isLoading = true;
},
/**
* Sets isLoading to false.
* Sets hasError to false.
* Sets the received data
* Sets the received pagination information
* @param {Object} state
* @param {Object} resp
*/
[types.RECEIVE_RELEASES_SUCCESS](state, { data, pageInfo }) {
state.hasError = false;
state.isLoading = false;
state.releases = data;
state.pageInfo = pageInfo;
},
/**
* Sets isLoading to false.
* Sets hasError to true.
* Resets the data
* @param {Object} state
* @param {Object} data
*/
[types.RECEIVE_RELEASES_ERROR](state) {
state.isLoading = false;
state.releases = [];
state.hasError = true;
state.pageInfo = {};
},
[types.SET_SORTING](state, sorting) {
state.sorting = { ...state.sorting, ...sorting };
},
};
import { DESCENDING_ORDER, RELEASED_AT } from '../../../constants';
export default ({
projectId,
projectPath,
documentationPath,
illustrationPath,
newReleasePath = '',
}) => ({
projectId,
projectPath,
documentationPath,
illustrationPath,
newReleasePath,
isLoading: false,
hasError: false,
releases: [],
pageInfo: {},
sorting: {
sort: DESCENDING_ORDER,
orderBy: RELEASED_AT,
},
});
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 ReleaseSkeletonLoader from '~/releases/components/release_skeleton_loader.vue';
import ReleasesPagination from '~/releases/components/releases_pagination.vue';
import ReleasesSort from '~/releases/components/releases_sort.vue';
jest.mock('~/lib/utils/url_utility', () => ({
...jest.requireActual('~/lib/utils/url_utility'),
getParameterByName: jest.fn(),
}));
Vue.use(Vuex);
describe('app_index.vue', () => {
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,
),
},
}),
});
};
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);
});
};
const expectEmptyState = (shouldExist) => {
it(`${shouldExist ? 'renders' : 'does not render'} an empty state`, () => {
expect(findEmptyState().exists()).toBe(shouldExist);
});
};
const expectSuccessState = (shouldExist) => {
it(`${shouldExist ? 'renders' : 'does not render'} the success state`, () => {
expect(findSuccessState().exists()).toBe(shouldExist);
});
};
const expectPagination = (shouldExist) => {
it(`${shouldExist ? 'renders' : 'does not render'} the pagination controls`, () => {
expect(findPagination().exists()).toBe(shouldExist);
});
};
const expectNewReleaseButton = (shouldExist) => {
it(`${shouldExist ? 'renders' : 'does not render'} the "New release" button`, () => {
expect(findNewReleaseButton().exists()).toBe(shouldExist);
});
};
// 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 };
createComponent();
expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams);
},
);
});
describe('when the request to fetch releases has not yet completed', () => {
beforeEach(() => {
createComponent();
});
expectLoadingIndicator(true);
expectEmptyState(false);
expectSuccessState(false);
expectPagination(false);
});
describe('when the request fails', () => {
beforeEach(() => {
createComponent({
state: {
isLoading: false,
hasError: true,
},
});
});
expectLoadingIndicator(false);
expectEmptyState(false);
expectSuccessState(false);
expectPagination(true);
});
describe('when the request succeeds but returns no releases', () => {
beforeEach(() => {
createComponent({
state: {
isLoading: false,
},
});
});
expectLoadingIndicator(false);
expectEmptyState(true);
expectSuccessState(false);
expectPagination(true);
});
describe('when the request succeeds and includes at least one release', () => {
beforeEach(() => {
createComponent({
state: {
isLoading: false,
releases: [{}],
},
});
});
expectLoadingIndicator(false);
expectEmptyState(false);
expectSuccessState(true);
expectPagination(true);
});
describe('sorting', () => {
beforeEach(() => {
createComponent();
});
it('renders the sort controls', () => {
expect(findSortControls().exists()).toBe(true);
});
it('calls the fetchReleases store method when the sort is updated', () => {
fetchReleasesSpy.mockClear();
findSortControls().vm.$emit('sort:changed');
expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
});
});
describe('"New release" button', () => {
describe('when the user is allowed to create releases', () => {
const newReleasePath = 'path/to/new/release/page';
beforeEach(() => {
createComponent({ state: { newReleasePath } });
});
expectNewReleaseButton(true);
it('renders the button with the correct href', () => {
expect(findNewReleaseButton().attributes('href')).toBe(newReleasePath);
});
});
describe('when the user is not allowed to create releases', () => {
beforeEach(() => {
createComponent();
});
expectNewReleaseButton(false);
});
});
describe("when the browser's back button is pressed", () => {
beforeEach(() => {
urlParams = {
before: 'before_param_value',
};
createComponent();
fetchReleasesSpy.mockClear();
window.dispatchEvent(new PopStateEvent('popstate'));
});
it('calls the fetchRelease store method with the parameters from the URL query', () => {
expect(fetchReleasesSpy).toHaveBeenCalledTimes(1);
expect(fetchReleasesSpy).toHaveBeenCalledWith(expect.anything(), urlParams);
});
});
});
import { GlKeysetPagination } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
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', () => {
let wrapper;
let indexModule;
const cursors = {
startCursor: 'startCursor',
endCursor: 'endCursor',
};
const projectPath = 'my/project';
const createComponent = (pageInfo) => {
indexModule = createIndexModule({ projectPath });
indexModule.state.pageInfo = pageInfo;
indexModule.actions.fetchReleases = jest.fn();
wrapper = mount(ReleasesPagination, {
store: createStore({
modules: {
index: indexModule,
},
featureFlags: {},
}),
});
};
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 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 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 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}`)],
]);
});
});
});
});
import { GlSorting, GlSortingItem } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import ReleasesSort from '~/releases/components/releases_sort.vue';
import createStore from '~/releases/stores';
import createIndexModule from '~/releases/stores/modules/index';
Vue.use(Vuex);
describe('~/releases/components/releases_sort.vue', () => {
let wrapper;
let store;
let indexModule;
const projectId = 8;
const createComponent = () => {
indexModule = createIndexModule({ projectId });
store = createStore({
modules: {
index: indexModule,
},
});
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();
});
it('has all the sortable items', () => {
expect(findSortingItems()).toHaveLength(wrapper.vm.sortOptions.length);
});
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();
});
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();
});
});
import { cloneDeep } from 'lodash';
import originalGraphqlReleasesResponse from 'test_fixtures/graphql/releases/graphql/queries/all_releases.query.graphql.json';
import testAction from 'helpers/vuex_action_helper';
import { PAGE_SIZE } from '~/releases/constants';
import allReleasesQuery from '~/releases/graphql/queries/all_releases.query.graphql';
import {
fetchReleases,
receiveReleasesError,
setSorting,
} from '~/releases/stores/modules/index/actions';
import * as types from '~/releases/stores/modules/index/mutation_types';
import createState from '~/releases/stores/modules/index/state';
import { gqClient, convertAllReleasesGraphQLResponse } from '~/releases/util';
describe('Releases State actions', () => {
let mockedState;
let graphqlReleasesResponse;
const projectPath = 'root/test-project';
const projectId = 19;
const before = 'testBeforeCursor';
const after = 'testAfterCursor';
beforeEach(() => {
mockedState = {
...createState({
projectId,
projectPath,
}),
};
graphqlReleasesResponse = cloneDeep(originalGraphqlReleasesResponse);
});
describe('fetchReleases', () => {
describe('GraphQL query variables', () => {
let vuexParams;
beforeEach(() => {
jest.spyOn(gqClient, 'query');
vuexParams = { dispatch: jest.fn(), commit: jest.fn(), state: mockedState };
});
describe('when neither a before nor an after parameter is provided', () => {
beforeEach(() => {
fetchReleases(vuexParams, { before: undefined, after: undefined });
});
it('makes a GraphQl query with a first variable', () => {
expect(gqClient.query).toHaveBeenCalledWith({
query: allReleasesQuery,
variables: { fullPath: projectPath, first: PAGE_SIZE, sort: 'RELEASED_AT_DESC' },
});
});
});
describe('when only a before parameter is provided', () => {
beforeEach(() => {
fetchReleases(vuexParams, { before, after: undefined });
});
it('makes a GraphQl query with last and before variables', () => {
expect(gqClient.query).toHaveBeenCalledWith({
query: allReleasesQuery,
variables: { fullPath: projectPath, last: PAGE_SIZE, before, sort: 'RELEASED_AT_DESC' },
});
});
});
describe('when only an after parameter is provided', () => {
beforeEach(() => {
fetchReleases(vuexParams, { before: undefined, after });
});
it('makes a GraphQl query with first and after variables', () => {
expect(gqClient.query).toHaveBeenCalledWith({
query: allReleasesQuery,
variables: { fullPath: projectPath, first: PAGE_SIZE, after, sort: 'RELEASED_AT_DESC' },
});
});
});
describe('when both before and after parameters are provided', () => {
it('throws an error', () => {
const callFetchReleases = () => {
fetchReleases(vuexParams, { before, after });
};
expect(callFetchReleases).toThrowError(
'Both a `before` and an `after` parameter were provided to fetchReleases. These parameters cannot be used together.',
);
});
});
describe('when the sort parameters are provided', () => {
it.each`
sort | orderBy | ReleaseSort
${'asc'} | ${'released_at'} | ${'RELEASED_AT_ASC'}
${'desc'} | ${'released_at'} | ${'RELEASED_AT_DESC'}
${'asc'} | ${'created_at'} | ${'CREATED_ASC'}
${'desc'} | ${'created_at'} | ${'CREATED_DESC'}
`(
'correctly sets $ReleaseSort based on $sort and $orderBy',
({ sort, orderBy, ReleaseSort }) => {
mockedState.sorting.sort = sort;
mockedState.sorting.orderBy = orderBy;
fetchReleases(vuexParams, { before: undefined, after: undefined });
expect(gqClient.query).toHaveBeenCalledWith({
query: allReleasesQuery,
variables: { fullPath: projectPath, first: PAGE_SIZE, sort: ReleaseSort },
});
},
);
});
});
describe('when the request is successful', () => {
beforeEach(() => {
jest.spyOn(gqClient, 'query').mockResolvedValue(graphqlReleasesResponse);
});
it(`commits ${types.REQUEST_RELEASES} and ${types.RECEIVE_RELEASES_SUCCESS}`, () => {
const convertedResponse = convertAllReleasesGraphQLResponse(graphqlReleasesResponse);
return testAction(
fetchReleases,
{},
mockedState,
[
{
type: types.REQUEST_RELEASES,
},
{
type: types.RECEIVE_RELEASES_SUCCESS,
payload: {
data: convertedResponse.data,
pageInfo: convertedResponse.paginationInfo,
},
},
],
[],
);
});
});
describe('when the request fails', () => {
beforeEach(() => {
jest.spyOn(gqClient, 'query').mockRejectedValue(new Error('Something went wrong!'));
});
it(`commits ${types.REQUEST_RELEASES} and dispatch receiveReleasesError`, () => {
return testAction(
fetchReleases,
{},
mockedState,
[
{
type: types.REQUEST_RELEASES,
},
],
[
{
type: 'receiveReleasesError',
},
],
);
});
});
});
describe('receiveReleasesError', () => {
it('should commit RECEIVE_RELEASES_ERROR mutation', () => {
return testAction(
receiveReleasesError,
null,
mockedState,
[{ type: types.RECEIVE_RELEASES_ERROR }],
[],
);
});
});
describe('setSorting', () => {
it('should commit SET_SORTING', () => {
return testAction(
setSorting,
{ orderBy: 'released_at', sort: 'asc' },
null,
[{ type: types.SET_SORTING, payload: { orderBy: 'released_at', sort: 'asc' } }],
[],
);
});
});
});
import state from '~/releases/stores/modules/index/state';
export const resetStore = (store) => {
store.replaceState(state());
};
import originalRelease from 'test_fixtures/api/releases/release.json';
import graphqlReleasesResponse from 'test_fixtures/graphql/releases/graphql/queries/all_releases.query.graphql.json';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import * as types from '~/releases/stores/modules/index/mutation_types';
import mutations from '~/releases/stores/modules/index/mutations';
import createState from '~/releases/stores/modules/index/state';
import { convertAllReleasesGraphQLResponse } from '~/releases/util';
const originalReleases = [originalRelease];
describe('Releases Store Mutations', () => {
let stateCopy;
let pageInfo;
let releases;
beforeEach(() => {
stateCopy = createState({});
pageInfo = convertAllReleasesGraphQLResponse(graphqlReleasesResponse).paginationInfo;
releases = convertObjectPropsToCamelCase(originalReleases, { deep: true });
});
describe('REQUEST_RELEASES', () => {
it('sets isLoading to true', () => {
mutations[types.REQUEST_RELEASES](stateCopy);
expect(stateCopy.isLoading).toEqual(true);
});
});
describe('RECEIVE_RELEASES_SUCCESS', () => {
beforeEach(() => {
mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, {
pageInfo,
data: releases,
});
});
it('sets is loading to false', () => {
expect(stateCopy.isLoading).toEqual(false);
});
it('sets hasError to false', () => {
expect(stateCopy.hasError).toEqual(false);
});
it('sets data', () => {
expect(stateCopy.releases).toEqual(releases);
});
it('sets pageInfo', () => {
expect(stateCopy.pageInfo).toEqual(pageInfo);
});
});
describe('RECEIVE_RELEASES_ERROR', () => {
it('resets data', () => {
mutations[types.RECEIVE_RELEASES_SUCCESS](stateCopy, {
pageInfo,
data: releases,
});
mutations[types.RECEIVE_RELEASES_ERROR](stateCopy);
expect(stateCopy.isLoading).toEqual(false);
expect(stateCopy.releases).toEqual([]);
expect(stateCopy.pageInfo).toEqual({});
});
});
describe('SET_SORTING', () => {
it('should merge the sorting object with sort value', () => {
mutations[types.SET_SORTING](stateCopy, { sort: 'asc' });
expect(stateCopy.sorting).toEqual({ ...stateCopy.sorting, sort: 'asc' });
});
it('should merge the sorting object with order_by value', () => {
mutations[types.SET_SORTING](stateCopy, { orderBy: 'created_at' });
expect(stateCopy.sorting).toEqual({ ...stateCopy.sorting, orderBy: 'created_at' });
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment