Commit c54a30c1 authored by Martin Wortschack's avatar Martin Wortschack

Merge branch 'devops-adoption-remove-unused-code' into 'master'

Remove add button from Devops Adoption

See merge request gitlab-org/gitlab!64400
parents 224284bb 7c30991b
<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,
......@@ -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>
......@@ -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,
......@@ -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,
......
......@@ -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';
......
......@@ -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,
......@@ -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);
});
......@@ -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);
});
......
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';
......@@ -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,
},
......@@ -27,7 +24,6 @@ describe('DevopsAdoptionEmptyState', () => {
};
const findEmptyState = () => wrapper.find(GlEmptyState);
const findEmptyStateAction = () => findEmptyState().find(GlButton);
afterEach(() => {
wrapper.destroy();
......@@ -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);
});
});
});
});
});
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