Commit 6fb4ce1d authored by Paul Gascou-Vaillancourt's avatar Paul Gascou-Vaillancourt Committed by Kushal Pandya

Improve loading UX in the License Management list

- Submit button to add a policy now uses the success variant
- When adding a new policy, the cancel button is disabled and the
loading icon is shown in the button itself, rather than replacing
the whole list while it is being updated
- When updating or removing a policy, the loading icon is shown next to
the policy's dropdown
parent db0d1c9b
<script>
import { GlButton } from '@gitlab/ui';
import LoadingButton from '~/vue_shared/components/loading_button.vue';
import { LICENSE_APPROVAL_STATUS } from '../constants';
import AddLicenseFormDropdown from './add_license_form_dropdown.vue';
import { s__ } from '~/locale';
......@@ -9,6 +10,7 @@ export default {
components: {
AddLicenseFormDropdown,
GlButton,
LoadingButton,
},
LICENSE_APPROVAL_STATUS,
approvalStatusOptions: [
......@@ -21,6 +23,11 @@ export default {
required: false,
default: () => [],
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
......@@ -42,7 +49,6 @@ export default {
newStatus: this.approvalStatus,
license: { name: this.licenseName },
});
this.closeForm();
},
closeForm() {
this.$emit('closeForm');
......@@ -80,16 +86,16 @@ export default {
</label>
</div>
</div>
<gl-button
<loading-button
class="js-submit"
variant="default"
:disabled="submitDisabled"
:loading="loading"
container-class="btn btn-success btn-align-content d-inline-flex"
:label="s__('LicenseCompliance|Submit')"
data-qa-selector="add_license_submit_button"
@click="addLicense"
>
{{ s__('LicenseCompliance|Submit') }}
</gl-button>
<gl-button class="js-cancel" variant="default" @click="closeForm">
/>
<gl-button class="js-cancel" variant="default" :disabled="loading" @click="closeForm">
{{ s__('LicenseCompliance|Cancel') }}
</gl-button>
</div>
......
<script>
import { mapActions } from 'vuex';
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { GlDropdown, GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
import { getIssueStatusFromLicenseStatus } from 'ee/vue_shared/license_management/store/utils';
import { s__ } from '~/locale';
import Icon from '~/vue_shared/components/icon.vue';
......@@ -17,6 +17,7 @@ export default {
components: {
GlDropdown,
GlDropdownItem,
GlLoadingIcon,
Icon,
IssueStatusIcon,
},
......@@ -28,6 +29,11 @@ export default {
Boolean(license.name) &&
Object.values(LICENSE_APPROVAL_STATUS).includes(license.approvalStatus),
},
loading: {
type: Boolean,
required: false,
default: false,
},
},
LICENSE_APPROVAL_STATUS,
[LICENSE_APPROVAL_STATUS.APPROVED]: s__('LicenseCompliance|Allowed'),
......@@ -61,8 +67,10 @@ export default {
<span class="js-license-name" data-qa-selector="license_name_content">{{ license.name }}</span>
<div class="float-right">
<div class="d-flex">
<gl-loading-icon v-if="loading" class="js-loading-icon d-flex align-items-center mr-2" />
<gl-dropdown
:text="dropdownText"
:disabled="loading"
toggle-class="d-flex justify-content-between align-items-center"
right
>
......@@ -76,6 +84,7 @@ export default {
</gl-dropdown-item>
</gl-dropdown>
<button
:disabled="loading"
class="btn btn-blank js-remove-button"
type="button"
data-toggle="modal"
......
<script>
import { mapState, mapActions } from 'vuex';
import { mapState, mapGetters, mapActions } from 'vuex';
import { GlButton, GlLoadingIcon } from '@gitlab/ui';
import { s__ } from '~/locale';
import AddLicenseForm from './components/add_license_form.vue';
......@@ -38,6 +38,21 @@ export default {
},
computed: {
...mapState(LICENSE_MANAGEMENT, ['managedLicenses', 'isLoadingManagedLicenses', 'isAdmin']),
...mapGetters(LICENSE_MANAGEMENT, [
'isLicenseBeingUpdated',
'hasPendingLicenses',
'isAddingNewLicense',
]),
showLoadingSpinner() {
return this.isLoadingManagedLicenses && !this.hasPendingLicenses;
},
},
watch: {
isAddingNewLicense(isAddingNewLicense) {
if (!isAddingNewLicense) {
this.closeAddLicenseForm();
}
},
},
mounted() {
this.setAPISettings({
......@@ -67,7 +82,7 @@ export default {
};
</script>
<template>
<gl-loading-icon v-if="isLoadingManagedLicenses" />
<gl-loading-icon v-if="showLoadingSpinner" />
<div v-else class="license-management">
<delete-confirmation-modal v-if="isAdmin" />
......@@ -108,6 +123,7 @@ export default {
<div v-if="formIsOpen" class="prepend-top-default append-bottom-default">
<add-license-form
:managed-licenses="managedLicenses"
:loading="isAddingNewLicense"
@addLicense="setLicenseApproval"
@closeForm="closeAddLicenseForm"
/>
......@@ -115,7 +131,11 @@ export default {
</template>
<template #default="{ listItem }">
<admin-license-management-row v-if="isAdmin" :license="listItem" />
<admin-license-management-row
v-if="isAdmin"
:license="listItem"
:loading="isLicenseBeingUpdated(listItem.id)"
/>
<license-management-row v-else :license="listItem" />
</template>
</paginated-list>
......
......@@ -18,9 +18,11 @@ export const resetLicenseInModal = ({ commit }) => {
export const requestDeleteLicense = ({ commit }) => {
commit(types.REQUEST_DELETE_LICENSE);
};
export const receiveDeleteLicense = ({ commit, dispatch }) => {
export const receiveDeleteLicense = ({ commit, dispatch }, id) => {
commit(types.RECEIVE_DELETE_LICENSE);
dispatch('fetchManagedLicenses');
return dispatch('fetchManagedLicenses').then(() => {
dispatch('removePendingLicense', id);
});
};
export const receiveDeleteLicenseError = ({ commit }, error) => {
commit(types.RECEIVE_DELETE_LICENSE_ERROR, error);
......@@ -28,14 +30,16 @@ export const receiveDeleteLicenseError = ({ commit }, error) => {
export const deleteLicense = ({ dispatch, state }) => {
const licenseId = state.currentLicenseInModal.id;
dispatch('requestDeleteLicense');
dispatch('addPendingLicense', licenseId);
const endpoint = `${state.apiUrlManageLicenses}/${licenseId}`;
return axios
.delete(endpoint)
.then(() => {
dispatch('receiveDeleteLicense');
dispatch('receiveDeleteLicense', licenseId);
})
.catch(error => {
dispatch('receiveDeleteLicenseError', error);
dispatch('removePendingLicense', licenseId);
});
};
......@@ -89,7 +93,7 @@ export const fetchParsedLicenseReport = ({ dispatch, state }) => {
export const requestSetLicenseApproval = ({ commit }) => {
commit(types.REQUEST_SET_LICENSE_APPROVAL);
};
export const receiveSetLicenseApproval = ({ commit, dispatch, state }) => {
export const receiveSetLicenseApproval = ({ commit, dispatch, state }, id) => {
commit(types.RECEIVE_SET_LICENSE_APPROVAL);
// If we have the licenses API endpoint, fetch from there. This corresponds
// to the cases that we're viewing the merge request or pipeline pages.
......@@ -97,10 +101,11 @@ export const receiveSetLicenseApproval = ({ commit, dispatch, state }) => {
// the project settings page.
// https://gitlab.com/gitlab-org/gitlab/issues/201867
if (state.licensesApiPath) {
dispatch('fetchParsedLicenseReport');
} else {
dispatch('fetchManagedLicenses');
return dispatch('fetchParsedLicenseReport');
}
return dispatch('fetchManagedLicenses').then(() => {
dispatch('removePendingLicense', id);
});
};
export const receiveSetLicenseApprovalError = ({ commit }, error) => {
commit(types.RECEIVE_SET_LICENSE_APPROVAL_ERROR, error);
......@@ -110,12 +115,23 @@ export const setIsAdmin = ({ commit }, payload) => {
commit(types.SET_IS_ADMIN, payload);
};
export const addPendingLicense = ({ state, commit }, id = null) => {
if (!state.pendingLicenses.includes(id)) {
commit(types.ADD_PENDING_LICENSE, id);
}
};
export const removePendingLicense = ({ commit }, id = null) => {
commit(types.REMOVE_PENDING_LICENSE, id);
};
export const setLicenseApproval = ({ dispatch, state }, payload) => {
const { apiUrlManageLicenses } = state;
const { license, newStatus } = payload;
const { id, name } = license;
dispatch('requestSetLicenseApproval');
dispatch('addPendingLicense', id);
let request;
......@@ -131,10 +147,11 @@ export const setLicenseApproval = ({ dispatch, state }, payload) => {
return request
.then(() => {
dispatch('receiveSetLicenseApproval');
dispatch('receiveSetLicenseApproval', id);
})
.catch(error => {
dispatch('receiveSetLicenseApprovalError', error);
dispatch('removePendingLicense', id);
});
};
export const approveLicense = ({ dispatch }, license) => {
......
......@@ -3,6 +3,12 @@ import { LICENSE_APPROVAL_STATUS } from '../constants';
export const isLoading = state => state.isLoadingManagedLicenses || state.isLoadingLicenseReport;
export const isLicenseBeingUpdated = state => (id = null) => state.pendingLicenses.includes(id);
export const isAddingNewLicense = (_, getters) => getters.isLicenseBeingUpdated();
export const hasPendingLicenses = state => state.pendingLicenses.length > 0;
export const licenseReport = state => state.newLicenses;
export const licenseSummaryText = (state, getters) => {
......
......@@ -14,6 +14,8 @@ export const RESET_LICENSE_IN_MODAL = 'RESET_LICENSE_IN_MODAL';
export const SET_API_SETTINGS = 'SET_API_SETTINGS';
export const SET_LICENSE_IN_MODAL = 'SET_LICENSE_IN_MODAL';
export const SET_IS_ADMIN = 'SET_IS_ADMIN';
export const ADD_PENDING_LICENSE = 'ADD_PENDING_LICENSE';
export const REMOVE_PENDING_LICENSE = 'REMOVE_PENDING_LICENSE';
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
......@@ -96,4 +96,10 @@ export default {
currentLicenseInModal: null,
});
},
[types.ADD_PENDING_LICENSE](state, id) {
state.pendingLicenses.push(id);
},
[types.REMOVE_PENDING_LICENSE](state, id) {
state.pendingLicenses = state.pendingLicenses.filter(pendingLicense => pendingLicense !== id);
},
};
......@@ -7,6 +7,7 @@ export default () => ({
isDeleting: false,
isLoadingLicenseReport: false,
isLoadingManagedLicenses: false,
pendingLicenses: [],
isSaving: false,
loadLicenseReportError: false,
loadManagedLicensesError: false,
......
---
title: Improve loading UX in the License Management list
merge_request: 25620
author:
type: changed
......@@ -7,6 +7,9 @@ describe('AddLicenseForm', () => {
const Component = Vue.extend(LicenseIssueBody);
let vm;
const findSubmitButton = () => vm.$el.querySelector('.js-submit');
const findCancelButton = () => vm.$el.querySelector('.js-cancel');
beforeEach(() => {
vm = mountComponent(Component);
});
......@@ -23,7 +26,7 @@ describe('AddLicenseForm', () => {
vm.licenseName = name;
Vue.nextTick(() => {
const linkEl = vm.$el.querySelector('.js-submit');
const linkEl = findSubmitButton();
linkEl.click();
expect(vm.$emit).toHaveBeenCalledWith('addLicense', {
......@@ -31,13 +34,12 @@ describe('AddLicenseForm', () => {
license: { name },
});
expect(vm.$emit).toHaveBeenCalledWith('closeForm');
done();
});
});
it('clicking the Cancel button closes the form', () => {
const linkEl = vm.$el.querySelector('.js-cancel');
const linkEl = findCancelButton();
jest.spyOn(vm, '$emit').mockImplementation(() => {});
linkEl.click();
......@@ -124,10 +126,24 @@ describe('AddLicenseForm', () => {
Vue.nextTick(() => {
expect(vm.submitDisabled).toBe(true);
const submitButton = vm.$el.querySelector('.js-submit');
const submitButton = findSubmitButton();
expect(submitButton).not.toBeNull();
expect(submitButton.disabled).toBe(true);
done();
});
});
it('disables submit and cancel while a new license is being added', done => {
vm.loading = true;
Vue.nextTick(() => {
const submitButton = findSubmitButton();
const cancelButton = findCancelButton();
expect(submitButton).not.toBeNull();
expect(submitButton.disabled).toBe(true);
expect(cancelButton).not.toBeNull();
expect(cancelButton.disabled).toBe(true);
done();
});
});
......
......@@ -19,8 +19,15 @@ describe('AdminLicenseManagementRow', () => {
let store;
let actions;
const createComponent = (props = { license: approvedLicense }) => {
vm = mountComponentWithStore(Component, { props, store });
};
const findNthDropdown = num => [...vm.$el.querySelectorAll('.dropdown-item')][num];
const findNthDropdownIcon = num => findNthDropdown(num).querySelector('svg');
const findLoadingIcon = () => vm.$el.querySelector('.js-loading-icon');
const findDropdownToggle = () => vm.$el.querySelector('.dropdown > button');
const findRemoveButton = () => vm.$el.querySelector('.js-remove-button');
beforeEach(() => {
actions = {
......@@ -39,9 +46,7 @@ describe('AdminLicenseManagementRow', () => {
},
});
const props = { license: approvedLicense };
vm = mountComponentWithStore(Component, { props, store });
createComponent();
});
afterEach(() => {
......@@ -120,7 +125,7 @@ describe('AdminLicenseManagementRow', () => {
describe('interaction', () => {
it('triggering setLicenseInModal by clicking the cancel button', () => {
const linkEl = vm.$el.querySelector('.js-remove-button');
const linkEl = findRemoveButton();
linkEl.click();
expect(actions.setLicenseInModal).toHaveBeenCalled();
......@@ -159,7 +164,7 @@ describe('AdminLicenseManagementRow', () => {
});
it('renders the removal button', () => {
const buttonEl = vm.$el.querySelector('.js-remove-button');
const buttonEl = findRemoveButton();
expect(buttonEl).not.toBeNull();
expect(buttonEl.querySelector('.ic-remove')).not.toBeNull();
......@@ -186,5 +191,18 @@ describe('AdminLicenseManagementRow', () => {
expect(secondOption).not.toBeNull();
expect(secondOption.innerText.trim()).toBe('Denied');
});
it('does not show a loading icon and enables both the dropdown and the remove button by default', () => {
expect(findLoadingIcon()).toBeNull();
expect(findDropdownToggle().disabled).toBe(false);
expect(findRemoveButton().disabled).toBe(false);
});
it('shows a loading icon and disables both the dropdown and the remove button while loading', () => {
createComponent({ license: approvedLicense, loading: true });
expect(findLoadingIcon()).not.toBeNull();
expect(findDropdownToggle().disabled).toBe(true);
expect(findRemoveButton().disabled).toBe(true);
});
});
});
......@@ -31,11 +31,17 @@ const PaginatedListMock = {
const noop = () => {};
const createComponent = ({ state, props, actionMocks, isAdmin }) => {
const createComponent = ({ state, getters, props, actionMocks, isAdmin }) => {
const fakeStore = new Vuex.Store({
modules: {
licenseManagement: {
namespaced: true,
getters: {
isAddingNewLicense: () => false,
hasPendingLicenses: () => false,
isLicenseBeingUpdated: () => () => false,
...getters,
},
state: {
managedLicenses,
isLoadingManagedLicenses: true,
......@@ -76,11 +82,20 @@ describe('License Management', () => {
${'when admin'} | ${true}
${'when developer'} | ${false}
`('$desc', ({ isAdmin }) => {
it('when loading should render loading icon', () => {
it('should render loading icon during initial loading', () => {
createComponent({ state: { isLoadingManagedLicenses: true }, isAdmin });
expect(wrapper.find(GlLoadingIcon).exists()).toBe(true);
});
it('should render list of managed licenses while updating a license', () => {
createComponent({
state: { isLoadingManagedLicenses: true },
getters: { hasPendingLicenses: () => true },
isAdmin,
});
expect(wrapper.find({ name: 'PaginatedList' }).props('list')).toBe(managedLicenses);
});
describe('when not loading', () => {
beforeEach(() => {
createComponent({ state: { isLoadingManagedLicenses: false }, isAdmin });
......
......@@ -15,6 +15,11 @@ describe('License store actions', () => {
let axiosMock;
let licenseId;
let state;
let mockDispatch;
let mockCommit;
let store;
const expectDispatched = (...args) => expect(mockDispatch).toHaveBeenCalledWith(...args);
beforeEach(() => {
axiosMock = new MockAdapter(axios);
......@@ -24,6 +29,13 @@ describe('License store actions', () => {
currentLicenseInModal: approvedLicense,
};
licenseId = approvedLicense.id;
mockDispatch = jest.fn(() => Promise.resolve());
mockCommit = jest.fn();
store = {
state,
commit: mockCommit,
dispatch: mockDispatch,
};
});
afterEach(() => {
......@@ -102,16 +114,12 @@ describe('License store actions', () => {
});
describe('receiveDeleteLicense', () => {
it('commits RECEIVE_DELETE_LICENSE and dispatches fetchManagedLicenses', done => {
testAction(
actions.receiveDeleteLicense,
null,
state,
[{ type: mutationTypes.RECEIVE_DELETE_LICENSE }],
[{ type: 'fetchManagedLicenses' }],
)
.then(done)
.catch(done.fail);
it('commits RECEIVE_DELETE_LICENSE and dispatches fetchManagedLicenses and removePendingLicense', () => {
return actions.receiveDeleteLicense(store, licenseId).then(() => {
expect(mockCommit).toHaveBeenCalledWith(mutationTypes.RECEIVE_DELETE_LICENSE);
expectDispatched('fetchManagedLicenses');
expectDispatched('removePendingLicense', licenseId);
});
});
});
......@@ -139,41 +147,31 @@ describe('License store actions', () => {
endpointMock = axiosMock.onDelete(deleteUrl);
});
it('dispatches requestDeleteLicense and receiveDeleteLicense for successful response', done => {
it('dispatches requestDeleteLicense, addPendingLicense and receiveDeleteLicense for successful response', () => {
endpointMock.replyOnce(req => {
expect(req.url).toBe(deleteUrl);
return [200, ''];
});
testAction(
actions.deleteLicense,
null,
state,
[],
[{ type: 'requestDeleteLicense' }, { type: 'receiveDeleteLicense' }],
)
.then(done)
.catch(done.fail);
return actions.deleteLicense(store).then(() => {
expectDispatched('requestDeleteLicense');
expectDispatched('addPendingLicense', licenseId);
expectDispatched('receiveDeleteLicense', licenseId);
});
});
it('dispatches requestDeleteLicense and receiveDeleteLicenseError for error response', done => {
it('dispatches requestDeleteLicense, addPendingLicense, receiveDeleteLicenseError and removePendingLicense for error response', () => {
endpointMock.replyOnce(req => {
expect(req.url).toBe(deleteUrl);
return [500, ''];
});
testAction(
actions.deleteLicense,
null,
state,
[],
[
{ type: 'requestDeleteLicense' },
{ type: 'receiveDeleteLicenseError', payload: expect.any(Error) },
],
)
.then(done)
.catch(done.fail);
return actions.deleteLicense(store).then(() => {
expectDispatched('requestDeleteLicense');
expectDispatched('addPendingLicense', licenseId);
expectDispatched('receiveDeleteLicenseError', expect.any(Error));
expectDispatched('removePendingLicense', licenseId);
});
});
});
......@@ -207,16 +205,12 @@ describe('License store actions', () => {
});
describe('given the licensesApiPath is not provided', () => {
it('commits RECEIVE_SET_LICENSE_APPROVAL and dispatches fetchManagedLicenses', done => {
testAction(
actions.receiveSetLicenseApproval,
null,
state,
[{ type: mutationTypes.RECEIVE_SET_LICENSE_APPROVAL }],
[{ type: 'fetchManagedLicenses' }],
)
.then(done)
.catch(done.fail);
it('commits RECEIVE_SET_LICENSE_APPROVAL and dispatches fetchManagedLicenses and removePendingLicense', () => {
return actions.receiveSetLicenseApproval(store, licenseId).then(() => {
expect(mockCommit).toHaveBeenCalledWith(mutationTypes.RECEIVE_SET_LICENSE_APPROVAL);
expectDispatched('fetchManagedLicenses');
expectDispatched('removePendingLicense', licenseId);
});
});
});
});
......@@ -248,7 +242,7 @@ describe('License store actions', () => {
putEndpointMock = axiosMock.onPost(apiUrlManageLicenses);
});
it('dispatches requestSetLicenseApproval and receiveSetLicenseApproval for successful response', done => {
it('dispatches requestSetLicenseApproval, addPendingLicense and receiveSetLicenseApproval for successful response', () => {
putEndpointMock.replyOnce(req => {
const { approval_status, name } = JSON.parse(req.data);
......@@ -258,35 +252,25 @@ describe('License store actions', () => {
return [200, ''];
});
testAction(
actions.setLicenseApproval,
{ license: newLicense, newStatus },
state,
[],
[{ type: 'requestSetLicenseApproval' }, { type: 'receiveSetLicenseApproval' }],
)
.then(done)
.catch(done.fail);
return actions.setLicenseApproval(store, { license: newLicense, newStatus }).then(() => {
expectDispatched('requestSetLicenseApproval');
expectDispatched('addPendingLicense', undefined);
expectDispatched('receiveSetLicenseApproval', undefined);
});
});
it('dispatches requestSetLicenseApproval and receiveSetLicenseApprovalError for error response', done => {
it('dispatches requestSetLicenseApproval, addPendingLicense, receiveSetLicenseApprovalError and removePendingLicense for error response', () => {
putEndpointMock.replyOnce(req => {
expect(req.url).toBe(apiUrlManageLicenses);
return [500, ''];
});
testAction(
actions.setLicenseApproval,
{ license: newLicense, newStatus },
state,
[],
[
{ type: 'requestSetLicenseApproval' },
{ type: 'receiveSetLicenseApprovalError', payload: expect.any(Error) },
],
)
.then(done)
.catch(done.fail);
return actions.setLicenseApproval(store, { license: newLicense, newStatus }).then(() => {
expectDispatched('requestSetLicenseApproval');
expectDispatched('addPendingLicense', undefined);
expectDispatched('receiveSetLicenseApprovalError', expect.any(Error));
expectDispatched('removePendingLicense', undefined);
});
});
});
......@@ -299,7 +283,7 @@ describe('License store actions', () => {
patchEndpointMock = axiosMock.onPatch(licenseUrl);
});
it('dispatches requestSetLicenseApproval and receiveSetLicenseApproval for successful response', done => {
it('dispatches requestSetLicenseApproval, addPendingLicense and receiveSetLicenseApproval for successful response', () => {
patchEndpointMock.replyOnce(req => {
expect(req.url).toBe(licenseUrl);
const { approval_status, name } = JSON.parse(req.data);
......@@ -309,35 +293,29 @@ describe('License store actions', () => {
return [200, ''];
});
testAction(
actions.setLicenseApproval,
{ license: approvedLicense, newStatus },
state,
[],
[{ type: 'requestSetLicenseApproval' }, { type: 'receiveSetLicenseApproval' }],
)
.then(done)
.catch(done.fail);
return actions
.setLicenseApproval(store, { license: approvedLicense, newStatus })
.then(() => {
expectDispatched('requestSetLicenseApproval');
expectDispatched('addPendingLicense', approvedLicense.id);
expectDispatched('receiveSetLicenseApproval', approvedLicense.id);
});
});
it('dispatches requestSetLicenseApproval and receiveSetLicenseApprovalError for error response', done => {
it('dispatches requestSetLicenseApproval, addPendingLicense, receiveSetLicenseApprovalError and removePendingLicense for error response', () => {
patchEndpointMock.replyOnce(req => {
expect(req.url).toBe(licenseUrl);
return [500, ''];
});
testAction(
actions.setLicenseApproval,
{ license: approvedLicense, newStatus },
state,
[],
[
{ type: 'requestSetLicenseApproval' },
{ type: 'receiveSetLicenseApprovalError', payload: expect.any(Error) },
],
)
.then(done)
.catch(done.fail);
return actions
.setLicenseApproval(store, { license: approvedLicense, newStatus })
.then(() => {
expectDispatched('requestSetLicenseApproval');
expectDispatched('addPendingLicense', approvedLicense.id);
expectDispatched('receiveSetLicenseApprovalError', expect.any(Error));
expectDispatched('removePendingLicense', approvedLicense.id);
});
});
});
});
......
......@@ -28,6 +28,60 @@ describe('getters', () => {
});
});
describe('isLicenseBeingUpdated', () => {
beforeEach(() => {
state = createState();
});
it.each([5, null])('returns true if given license is being updated', licenseId => {
state.pendingLicenses = [licenseId];
expect(getters.isLicenseBeingUpdated(state)(licenseId)).toBe(true);
});
it('returns true if a new license is being added and no param is passed to the getter', () => {
state.pendingLicenses = [null];
expect(getters.isLicenseBeingUpdated(state)()).toBe(true);
});
it.each`
pendingLicenses | queriedLicense
${[null]} | ${5}
${[5]} | ${null}
${[5]} | ${undefined}
`(
'returns false if given license is not being updated',
({ pendingLicenses, queriedLicense }) => {
state.pendingLicenses = pendingLicenses;
expect(getters.isLicenseBeingUpdated(state)(queriedLicense)).toBe(false);
},
);
});
describe('isAddingNewLicense', () => {
it.each([true, false])('calls isLicenseBeingUpdated internally', returnValue => {
const isLicenseBeingUpdatedMock = jest.fn().mockImplementation(() => returnValue);
expect(
getters.isAddingNewLicense({}, { isLicenseBeingUpdated: isLicenseBeingUpdatedMock }),
).toBe(returnValue);
});
});
describe('hasPendingLicenses', () => {
it('returns true if there are some pending licenses', () => {
state = createState();
state.pendingLicenses = [null];
expect(getters.hasPendingLicenses(state)).toBe(true);
});
it('returns false if there are no pending licenses', () => {
state = createState();
state.pendingLicenses = [];
expect(getters.hasPendingLicenses(state)).toBe(false);
});
});
describe('licenseReport', () => {
it('should return the new licenses from the state', () => {
const newLicenses = { test: 'foo' };
......
......@@ -276,4 +276,31 @@ describe('License store mutations', () => {
expect(store.state.licenseManagement.isLoadingLicenseReport).toBe(false);
});
});
describe('ADD_PENDING_LICENSE', () => {
it('appends given id to pendingLicenses', () => {
store.commit(`licenseManagement/${types.ADD_PENDING_LICENSE}`, 5);
expect(store.state.licenseManagement.pendingLicenses).toEqual([5]);
store.commit(`licenseManagement/${types.ADD_PENDING_LICENSE}`, null);
expect(store.state.licenseManagement.pendingLicenses).toEqual([5, null]);
});
});
describe('REMOVE_PENDING_LICENSE', () => {
beforeEach(() => {
store.replaceState({
...store.state,
licenseManagement: {
pendingLicenses: [5, null],
},
});
});
it('appends given id to pendingLicenses', () => {
store.commit(`licenseManagement/${types.REMOVE_PENDING_LICENSE}`, null);
expect(store.state.licenseManagement.pendingLicenses).toEqual([5]);
store.commit(`licenseManagement/${types.REMOVE_PENDING_LICENSE}`, 5);
expect(store.state.licenseManagement.pendingLicenses).toEqual([]);
});
});
});
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