Skip to content
Snippets Groups Projects
Commit 7c30991b authored by Brandon Labuschagne's avatar Brandon Labuschagne
Browse files

Remove add button from Devops Adoption

Along with this, remove all of the dormant
code associated to the devops adoption
add / remove modal

Changelog: fixed
EE: true
parent 11731870
No related branches found
No related tags found
No related merge requests found
<script>
import { GlEmptyState, GlButton, GlModalDirective } from '@gitlab/ui';
import { DEVOPS_ADOPTION_STRINGS, DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from '../constants';
import { GlEmptyState, GlModalDirective } from '@gitlab/ui';
import { DEVOPS_ADOPTION_STRINGS } from '../constants';
 
export default {
name: 'DevopsAdoptionEmptyState',
components: {
GlEmptyState,
GlButton,
},
directives: {
GlModal: GlModalDirective,
},
inject: ['emptyStateSvgPath'],
i18n: DEVOPS_ADOPTION_STRINGS.emptyState,
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
props: {
hasGroupsData: {
type: Boolean,
Loading
Loading
@@ -27,15 +25,5 @@ export default {
:title="$options.i18n.title"
:description="$options.i18n.description"
:svg-path="emptyStateSvgPath"
>
<template #actions>
<gl-button
v-gl-modal="$options.devopsSegmentModalId"
:disabled="!hasGroupsData"
variant="info"
@click="$emit('clear-selected-segment')"
>{{ $options.i18n.button }}</gl-button
>
</template>
</gl-empty-state>
/>
</template>
<script>
import {
GlFormGroup,
GlFormInput,
GlFormCheckboxTree,
GlModal,
GlAlert,
GlIcon,
GlLoadingIcon,
} from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import _ from 'lodash';
import { convertToGraphQLId, getIdFromGraphQLId, TYPE_GROUP } from '~/graphql_shared/utils';
import {
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
DEVOPS_ADOPTION_GROUP_LEVEL_LABEL,
} from '../constants';
import bulkEnableDevopsAdoptionNamespacesMutation from '../graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from '../graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
export default {
name: 'DevopsAdoptionSegmentModal',
components: {
GlModal,
GlFormGroup,
GlFormInput,
GlFormCheckboxTree,
GlAlert,
GlIcon,
GlLoadingIcon,
},
inject: {
isGroup: {
default: false,
},
groupGid: {
default: null,
},
},
props: {
groups: {
type: Array,
required: true,
},
enabledGroups: {
type: Array,
required: false,
default: () => [],
},
isLoading: {
type: Boolean,
required: false,
default: false,
},
},
i18n: DEVOPS_ADOPTION_STRINGS.modal,
data() {
const checkboxValuesFromEnabledGroups = this.enabledGroups.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
return {
checkboxValuesFromEnabledGroups,
checkboxValues: checkboxValuesFromEnabledGroups,
filter: '',
loadingAdd: false,
loadingDelete: false,
errors: [],
};
},
computed: {
loading() {
return this.loadingAdd || this.loadingDelete;
},
checkboxOptions() {
return this.groups.map(({ id, full_name }) => ({ label: full_name, value: id }));
},
cancelOptions() {
return {
button: {
text: this.$options.i18n.cancel,
attributes: [{ disabled: this.loading }],
},
};
},
primaryOptions() {
return {
button: {
text: this.$options.i18n.addingButton,
attributes: [
{
variant: 'info',
loading: this.loading,
disabled: !this.canSubmit,
},
],
},
callback: this.saveChanges,
};
},
canSubmit() {
return !this.anyChangesMade;
},
displayError() {
return this.errors[0];
},
modalTitle() {
return this.isGroup ? DEVOPS_ADOPTION_GROUP_LEVEL_LABEL : this.$options.i18n.addingTitle;
},
filteredOptions() {
return this.filter
? this.checkboxOptions.filter((option) =>
option.label.toLowerCase().includes(this.filter.toLowerCase()),
)
: this.checkboxOptions;
},
anyChangesMade() {
return _.isEqual(
_.sortBy(this.checkboxValues),
_.sortBy(this.checkboxValuesFromEnabledGroups),
);
},
},
watch: {
enabledGroups(newValues) {
if (!this.loading) {
this.checkboxValuesFromEnabledGroups = newValues.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
this.checkboxValues = this.checkboxValuesFromEnabledGroups;
}
},
},
methods: {
async saveChanges() {
await this.deleteMissingGroups();
await this.addNewGroups();
if (!this.errors.length) this.closeModal();
},
async addNewGroups() {
try {
const originalEnabledIds = this.enabledGroups.map((group) =>
getIdFromGraphQLId(group.namespace.id),
);
const namespaceIds = this.checkboxValues
.filter((id) => !originalEnabledIds.includes(id))
.map((id) => convertToGraphQLId(TYPE_GROUP, id));
if (namespaceIds.length) {
this.loadingAdd = true;
const {
data: {
bulkEnableDevopsAdoptionNamespaces: { errors },
},
} = await this.$apollo.mutate({
mutation: bulkEnableDevopsAdoptionNamespacesMutation,
variables: {
namespaceIds,
displayNamespaceId: this.groupGid,
},
update: (store, { data }) => {
const {
bulkEnableDevopsAdoptionNamespaces: { enabledNamespaces, errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsAdded', enabledNamespaces);
},
});
if (errors.length) {
this.errors = errors;
}
}
} catch (error) {
this.errors.push(this.$options.i18n.error);
Sentry.captureException(error);
} finally {
this.loadingAdd = false;
}
},
async deleteMissingGroups() {
try {
const removedGroupGids = this.enabledGroups
.filter(
(group) =>
!this.checkboxValues.includes(getIdFromGraphQLId(group.namespace.id)) &&
group.namespace.id !== this.groupGid,
)
.map((group) => group.id);
if (removedGroupGids.length) {
this.loadingDelete = true;
const {
data: {
disableDevopsAdoptionNamespace: { errors },
},
} = await this.$apollo.mutate({
mutation: disableDevopsAdoptionNamespaceMutation,
variables: {
id: removedGroupGids,
},
update: (store, { data }) => {
const {
disableDevopsAdoptionNamespace: { errors: requestErrors },
} = data;
if (!requestErrors.length) this.$emit('segmentsRemoved', removedGroupGids);
},
});
if (errors.length) {
this.errors = errors;
}
}
} catch (error) {
this.errors.push(this.$options.i18n.error);
Sentry.captureException(error);
} finally {
this.loadingDelete = false;
}
},
clearErrors() {
this.errors = [];
},
openModal() {
this.$refs.modal.show();
},
closeModal() {
this.$refs.modal.hide();
},
resetForm() {
this.filter = '';
this.$emit('trackModalOpenState', false);
},
},
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
};
</script>
<template>
<gl-modal
ref="modal"
:modal-id="$options.devopsSegmentModalId"
:title="modalTitle"
size="sm"
scrollable
:action-primary="primaryOptions.button"
:action-cancel="cancelOptions.button"
@primary.prevent="primaryOptions.callback"
@hidden="resetForm"
@show="$emit('trackModalOpenState', true)"
>
<gl-loading-icon v-if="isLoading" size="md" class="gl-mt-4" />
<div v-else>
<gl-alert v-if="errors.length" variant="danger" class="gl-mb-3" @dismiss="clearErrors">
{{ displayError }}
</gl-alert>
<gl-form-group class="gl-mb-3" data-testid="filter">
<gl-icon
name="search"
:size="18"
use-deprecated-sizes
class="gl-text-gray-300 gl-absolute gl-mt-3 gl-ml-3"
/>
<gl-form-input
v-model="filter"
class="gl-pl-7!"
type="text"
:placeholder="$options.i18n.filterPlaceholder"
:disabled="loading"
/>
</gl-form-group>
<gl-form-group class="gl-mb-0">
<gl-form-checkbox-tree
v-if="filteredOptions.length"
v-model="checkboxValues"
data-testid="groups"
:options="filteredOptions"
:hide-toggle-all="true"
:disabled="loading"
class="gl-p-3 gl-pb-0 gl-mb-2 gl-border-1 gl-border-solid gl-border-gray-100 gl-rounded-base"
/>
<gl-alert v-else variant="info" :dismissible="false" data-testid="filter-warning">
{{ $options.i18n.noResults }}
</gl-alert>
</gl-form-group>
</div>
</gl-modal>
</template>
Loading
Loading
@@ -11,7 +11,6 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
DEVOPS_ADOPTION_TABLE_TEST_IDS,
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_DESC_STORAGE_KEY,
Loading
Loading
@@ -71,7 +70,6 @@ export default {
...i18n,
removeButtonDisabled: DEVOPS_ADOPTION_TABLE_REMOVE_BUTTON_DISABLED,
},
devopsSegmentModalId: DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
devopsSegmentDeleteModalId: DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID,
testids: DEVOPS_ADOPTION_TABLE_TEST_IDS,
sortByStorageKey: DEVOPS_ADOPTION_SEGMENTS_TABLE_SORT_BY_STORAGE_KEY,
Loading
Loading
Loading
Loading
@@ -6,8 +6,6 @@ export const PER_PAGE = 20;
 
export const DEBOUNCE_DELAY = 500;
 
export const DEVOPS_ADOPTION_SEGMENT_MODAL_ID = 'devopsSegmentModal';
export const DEVOPS_ADOPTION_SEGMENT_DELETE_MODAL_ID = 'devopsSegmentDeleteModal';
 
export const DATE_TIME_FORMAT = 'yyyy-mm-dd HH:MM';
Loading
Loading
Loading
Loading
@@ -6,7 +6,6 @@ import VueApollo from 'vue-apollo';
import DevopsAdoptionAddDropdown from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_add_dropdown.vue';
import DevopsAdoptionApp from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_app.vue';
import DevopsAdoptionSection from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_section.vue';
import DevopsAdoptionSegmentModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_segment_modal.vue';
import {
DEVOPS_ADOPTION_STRINGS,
DEFAULT_POLLING_INTERVAL,
Loading
Loading
@@ -238,10 +237,6 @@ describe('DevopsAdoptionApp', () => {
await wrapper.vm.$nextTick();
});
 
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
});
Loading
Loading
@@ -273,10 +268,6 @@ describe('DevopsAdoptionApp', () => {
await waitForPromises();
});
 
it('does not render the segment modal', () => {
expect(wrapper.find(DevopsAdoptionSegmentModal).exists()).toBe(false);
});
it('does not render the devops section', () => {
expect(wrapper.find(DevopsAdoptionSection).exists()).toBe(false);
});
Loading
Loading
import { GlEmptyState, GlButton } from '@gitlab/ui';
import { shallowMount, mount } from '@vue/test-utils';
import { GlEmptyState } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import DevopsAdoptionEmptyState from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_empty_state.vue';
import {
DEVOPS_ADOPTION_STRINGS,
DEVOPS_ADOPTION_SEGMENT_MODAL_ID,
} from 'ee/analytics/devops_report/devops_adoption/constants';
import { DEVOPS_ADOPTION_STRINGS } from 'ee/analytics/devops_report/devops_adoption/constants';
 
const emptyStateSvgPath = 'illustrations/monitoring/getting_started.svg';
 
Loading
Loading
@@ -12,9 +9,9 @@ describe('DevopsAdoptionEmptyState', () => {
let wrapper;
 
const createComponent = (options = {}) => {
const { stubs = {}, props = {}, func = shallowMount } = options;
const { stubs = {}, props = {} } = options;
 
return func(DevopsAdoptionEmptyState, {
return shallowMount(DevopsAdoptionEmptyState, {
provide: {
emptyStateSvgPath,
},
Loading
Loading
@@ -27,7 +24,6 @@ describe('DevopsAdoptionEmptyState', () => {
};
 
const findEmptyState = () => wrapper.find(GlEmptyState);
const findEmptyStateAction = () => findEmptyState().find(GlButton);
 
afterEach(() => {
wrapper.destroy();
Loading
Loading
@@ -48,43 +44,4 @@ describe('DevopsAdoptionEmptyState', () => {
expect(emptyState.props('title')).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.title);
expect(emptyState.props('description')).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.description);
});
describe('action button', () => {
it('displays an overridden action button', () => {
wrapper = createComponent({ stubs: { GlEmptyState } });
const actionButton = findEmptyStateAction();
expect(actionButton.exists()).toBe(true);
expect(actionButton.text()).toBe(DEVOPS_ADOPTION_STRINGS.emptyState.button);
});
it('is enabled when there is group data', () => {
wrapper = createComponent({ stubs: { GlEmptyState } });
const actionButton = findEmptyStateAction();
expect(actionButton.props('disabled')).toBe(false);
});
it('is disabled when there is no group data', () => {
wrapper = createComponent({ stubs: { GlEmptyState }, props: { hasGroupsData: false } });
const actionButton = findEmptyStateAction();
expect(actionButton.props('disabled')).toBe(true);
});
it('calls the gl-modal show', async () => {
wrapper = createComponent({ func: mount });
const actionButton = findEmptyStateAction();
const rootEmit = jest.spyOn(wrapper.vm.$root, '$emit');
actionButton.trigger('click');
expect(rootEmit.mock.calls[0][0]).toContain('show');
expect(rootEmit.mock.calls[0][1]).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID);
});
});
});
import { GlModal, GlFormInput, GlSprintf, GlAlert, GlIcon, GlLoadingIcon } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import { createLocalVue } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import DevopsAdoptionSegmentModal from 'ee/analytics/devops_report/devops_adoption/components/devops_adoption_segment_modal.vue';
import { DEVOPS_ADOPTION_SEGMENT_MODAL_ID } from 'ee/analytics/devops_report/devops_adoption/constants';
import bulkEnableDevopsAdoptionNamespacesMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/bulk_enable_devops_adoption_namespaces.mutation.graphql';
import disableDevopsAdoptionNamespaceMutation from 'ee/analytics/devops_report/devops_adoption/graphql/mutations/disable_devops_adoption_namespace.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import {
groupNodes,
groupIds,
groupGids,
genericErrorMessage,
dataErrorMessage,
groupNodeLabelValues,
devopsAdoptionNamespaceData,
} from '../mock_data';
const localVue = createLocalVue();
Vue.use(VueApollo);
const mockEvent = { preventDefault: jest.fn() };
const mutate = jest.fn().mockResolvedValue({
data: {
bulkEnableDevopsAdoptionNamespaces: {
enabledNamespaces: [devopsAdoptionNamespaceData.nodes[0]],
errors: [],
},
disableDevopsAdoptionNamespace: {
enabledNamespaces: [devopsAdoptionNamespaceData.nodes[0]],
errors: [],
},
},
});
const mutateWithDataErrors = jest.fn().mockResolvedValue({
data: {
bulkEnableDevopsAdoptionNamespaces: {
errors: [dataErrorMessage],
enabledNamespaces: [],
},
disableDevopsAdoptionNamespace: {
errors: [dataErrorMessage],
enabledNamespaces: [],
},
},
});
const mutateLoading = jest.fn().mockResolvedValue(new Promise(() => {}));
const mutateWithErrors = jest.fn().mockRejectedValue(genericErrorMessage);
describe('DevopsAdoptionSegmentModal', () => {
let wrapper;
const createComponent = ({
deleteSegmentsSpy = mutate,
addSegmentsSpy = mutate,
props = {},
provide = {},
} = {}) => {
const mockApollo = createMockApollo([
[disableDevopsAdoptionNamespaceMutation, deleteSegmentsSpy],
[bulkEnableDevopsAdoptionNamespacesMutation, addSegmentsSpy],
]);
wrapper = shallowMountExtended(DevopsAdoptionSegmentModal, {
localVue,
apolloProvider: mockApollo,
propsData: {
groups: groupNodes,
...props,
},
provide,
stubs: {
GlSprintf,
},
});
wrapper.vm.$refs.modal.hide = jest.fn();
};
const findModal = () => wrapper.find(GlModal);
const actionButtonDisabledState = () => findModal().props('actionPrimary').attributes[0].disabled;
const cancelButtonDisabledState = () => findModal().props('actionCancel').attributes[0].disabled;
const actionButtonLoadingState = () => findModal().props('actionPrimary').attributes[0].loading;
const findAlert = () => findModal().find(GlAlert);
afterEach(() => {
wrapper.destroy();
});
it('contains the corrrect id', () => {
createComponent();
const modal = findModal();
expect(modal.exists()).toBe(true);
expect(modal.props('modalId')).toBe(DEVOPS_ADOPTION_SEGMENT_MODAL_ID);
});
describe('while loading', () => {
beforeEach(() => {
createComponent({ props: { isLoading: true } });
});
it('displays the loading icon', () => {
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true);
});
it('does not display the form', () => {
expect(wrapper.findByTestId('groups').exists()).toBe(false);
});
});
it('does not display the loading icon', () => {
createComponent();
expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false);
});
describe('modal title', () => {
it('contains the correct admin level title', () => {
createComponent();
const modal = findModal();
expect(modal.props('title')).toBe('Add/remove groups');
});
it('contains the corrrect group level title', () => {
createComponent({ provide: { isGroup: true } });
const modal = findModal();
expect(modal.props('title')).toBe('Add/remove sub-groups');
});
});
it.each`
enabledGroups | checkboxValues | disabled | condition | state
${[]} | ${[]} | ${true} | ${'no changes'} | ${'disables'}
${[]} | ${[1]} | ${false} | ${'changes'} | ${'enables'}
`(
'$state the primary action if there are $condition',
async ({ enabledGroups, disabled, checkboxValues }) => {
createComponent({ props: { enabledGroups } });
wrapper.setData({ checkboxValues });
await nextTick();
expect(actionButtonDisabledState()).toBe(disabled);
},
);
describe('displays the correct content', () => {
beforeEach(() => createComponent());
const isCorrectShape = (option) => {
const keys = Object.keys(option);
return keys.includes('label') && keys.includes('value');
};
it('contains the checkbox tree component', () => {
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.exists()).toBe(true);
const options = checkboxes.props('options');
expect(options.length).toBe(2);
expect(options.every(isCorrectShape)).toBe(true);
});
describe('filtering', () => {
describe('filter input field', () => {
it('contains the filter input', () => {
const filter = wrapper.findByTestId('filter');
expect(filter.exists()).toBe(true);
expect(filter.find(GlFormInput).exists()).toBe(true);
});
it('contains the filter icon', () => {
const icon = wrapper.findByTestId('filter').find(GlIcon);
expect(icon.exists()).toBe(true);
expect(icon.props('name')).toBe('search');
});
});
it.each`
filter | results
${''} | ${groupNodeLabelValues}
${'fo'} | ${[groupNodeLabelValues[0]]}
${'ar'} | ${[groupNodeLabelValues[1]]}
`(
'displays the correct results when filtering for value "$filter"',
async ({ filter, results }) => {
wrapper.setData({ filter });
await nextTick();
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.props('options')).toStrictEqual(results);
},
);
describe('when there are no filter results', () => {
beforeEach(async () => {
wrapper.setData({ filter: 'lalalala' });
await nextTick();
});
it('displays a warning message when there are no results', async () => {
const warning = wrapper.findByTestId('filter-warning');
expect(warning.exists()).toBe(true);
expect(warning.text()).toBe('No filter results.');
expect(warning.props('variant')).toBe('info');
});
it('hides the checkboxes', () => {
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.exists()).toBe(false);
});
});
});
it('does not display an error', () => {
expect(findAlert().exists()).toBe(false);
});
});
describe.each`
state | action | expected
${'opening'} | ${'show'} | ${true}
${'closing'} | ${'hidden'} | ${false}
`('$state the modal', ({ action, expected }) => {
beforeEach(() => {
createComponent();
findModal().vm.$emit(action);
});
it(`emits trackModalOpenState as ${expected}`, () => {
expect(wrapper.emitted('trackModalOpenState')).toStrictEqual([[expected]]);
});
});
describe('handles the form submission correctly when saving changes', () => {
const enableFirstGroup = { checkboxValues: [groupIds[0]] };
const enableSecondGroup = { checkboxValues: [groupIds[1]] };
const noEnabledGroups = { checkboxValues: [] };
const firstGroupEnabledData = [devopsAdoptionNamespaceData.nodes[0]];
const firstGroupId = [groupIds[0]];
const firstGroupGid = [groupGids[0]];
const secondGroupGid = [groupGids[1]];
describe('submitting the form', () => {
describe('while waiting for the mutation', () => {
beforeEach(() => {
createComponent({ mutationMock: mutateLoading });
wrapper.setData(enableFirstGroup);
});
it('disables the form inputs', async () => {
const checkboxes = wrapper.findByTestId('groups');
expect(checkboxes.attributes('disabled')).not.toBeDefined();
findModal().vm.$emit('primary', mockEvent);
await nextTick();
expect(checkboxes.attributes('disabled')).toBeDefined();
});
it('disables the cancel button', async () => {
expect(cancelButtonDisabledState()).toBe(false);
findModal().vm.$emit('primary', mockEvent);
await nextTick();
expect(cancelButtonDisabledState()).toBe(true);
});
it('sets the action button state to loading', async () => {
expect(actionButtonLoadingState()).toBe(false);
findModal().vm.$emit('primary', mockEvent);
await nextTick();
expect(actionButtonLoadingState()).toBe(true);
});
});
describe.each`
action | enabledGroups | newGroups | expectedAddGroupGids | expectedDeleteIds
${'adding'} | ${[]} | ${enableFirstGroup} | ${firstGroupGid} | ${[]}
${'removing'} | ${firstGroupEnabledData} | ${noEnabledGroups} | ${[]} | ${firstGroupId}
${'adding and removing'} | ${firstGroupEnabledData} | ${enableSecondGroup} | ${secondGroupGid} | ${firstGroupId}
`(
'$action groups',
({ enabledGroups, newGroups, expectedAddGroupGids, expectedDeleteIds }) => {
beforeEach(async () => {
createComponent({ props: { enabledGroups } });
wrapper.setData(newGroups);
findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
});
if (expectedAddGroupGids.length) {
it('submits the correct add request variables', () => {
expect(mutate).toHaveBeenCalledWith({
displayNamespaceId: null,
namespaceIds: expectedAddGroupGids,
});
});
it('emits segmentsAdded with the correct variables', () => {
const [params] = wrapper.emitted().segmentsAdded[0];
expect(params).toStrictEqual([devopsAdoptionNamespaceData.nodes[0]]);
});
}
if (expectedDeleteIds.length) {
it('submits the correct delete request variables', () => {
expect(mutate).toHaveBeenCalledWith({ id: expectedDeleteIds });
});
it('emits segmentsRemoved with the correct variables', () => {
const [params] = wrapper.emitted().segmentsRemoved[0];
expect(params).toStrictEqual(firstGroupId);
});
}
it('closes the modal after a successful mutation', () => {
expect(wrapper.vm.$refs.modal.hide).toHaveBeenCalled();
});
it('resets the filter', () => {
findModal().vm.$emit('hidden');
expect(wrapper.vm.filter).toBe('');
});
},
);
describe('error handling', () => {
it.each`
errorType | errorLocation | mutationSpy | message
${'generic'} | ${'top level'} | ${mutateWithErrors} | ${genericErrorMessage}
${'specific'} | ${'data'} | ${mutateWithDataErrors} | ${dataErrorMessage}
`(
'displays a $errorType error if the mutation has a $errorLocation error',
async ({ mutationSpy, message }) => {
createComponent({ addSegmentsSpy: mutationSpy, deleteSegmentsSpy: mutationSpy });
wrapper.setData(enableFirstGroup);
findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
const alert = findAlert();
expect(alert.exists()).toBe(true);
expect(alert.props('variant')).toBe('danger');
expect(alert.text()).toBe(message);
},
);
it('calls sentry on top level error', async () => {
const captureException = jest.spyOn(Sentry, 'captureException');
createComponent({
addSegmentsSpy: mutateWithErrors,
deleteSegmentsSpy: mutateWithErrors,
});
wrapper.setData(enableFirstGroup);
findModal().vm.$emit('primary', mockEvent);
await waitForPromises();
expect(captureException.mock.calls[0][0].networkError).toBe(genericErrorMessage);
});
});
});
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment