Commit 0fad9ea1 authored by Savas Vedova's avatar Savas Vedova

Fix state not being updated

- Emit refetch event on all cases
- Update selected vulnerabilities when vulnerability list changes
- Refactor tests a bit
parent 22926703
...@@ -67,13 +67,15 @@ export default { ...@@ -67,13 +67,15 @@ export default {
.then(() => { .then(() => {
toast(this.dismissalSuccessMessage()); toast(this.dismissalSuccessMessage());
this.$emit('deselect-all-vulnerabilities'); this.$emit('deselect-all-vulnerabilities');
this.$emit('refetch-vulnerabilities');
}) })
.catch(() => { .catch(() => {
createFlash( createFlash(
s__('SecurityReports|There was an error dismissing the vulnerabilities.'), s__('SecurityReports|There was an error dismissing the vulnerabilities.'),
'alert', 'alert',
); );
})
.finally(() => {
this.$emit('refetch-vulnerabilities');
}); });
}, },
}, },
......
...@@ -97,6 +97,15 @@ export default { ...@@ -97,6 +97,15 @@ export default {
filters() { filters() {
this.selectedVulnerabilities = {}; this.selectedVulnerabilities = {};
}, },
vulnerabilities(vulnerabilities) {
const ids = new Set(vulnerabilities.map(v => v.id));
Object.keys(this.selectedVulnerabilities).forEach(vulnerabilityId => {
if (!ids.has(vulnerabilityId)) {
this.$delete(this.selectedVulnerabilities, vulnerabilityId);
}
});
},
}, },
methods: { methods: {
deselectAllVulnerabilities() { deselectAllVulnerabilities() {
......
---
title: Fix dismissal state not being updated
merge_request: 30503
author:
type: fixed
...@@ -3,6 +3,7 @@ import SelectionSummary from 'ee/security_dashboard/components/selection_summary ...@@ -3,6 +3,7 @@ import SelectionSummary from 'ee/security_dashboard/components/selection_summary
import { GlFormSelect, GlButton } from '@gitlab/ui'; import { GlFormSelect, GlButton } from '@gitlab/ui';
import createFlash from '~/flash'; import createFlash from '~/flash';
import toast from '~/vue_shared/plugins/global_toast'; import toast from '~/vue_shared/plugins/global_toast';
import waitForPromises from 'helpers/wait_for_promises';
jest.mock('~/flash'); jest.mock('~/flash');
jest.mock('~/vue_shared/plugins/global_toast'); jest.mock('~/vue_shared/plugins/global_toast');
...@@ -26,6 +27,10 @@ describe('Selection Summary component', () => { ...@@ -26,6 +27,10 @@ describe('Selection Summary component', () => {
const formSelect = () => wrapper.find(GlFormSelect); const formSelect = () => wrapper.find(GlFormSelect);
const createComponent = ({ props = {}, data = defaultData, mocks = defaultMocks }) => { const createComponent = ({ props = {}, data = defaultData, mocks = defaultMocks }) => {
if (wrapper) {
throw new Error('Please avoid recreating components in the same spec');
}
spyMutate = mocks.$apollo.mutate; spyMutate = mocks.$apollo.mutate;
wrapper = mount(SelectionSummary, { wrapper = mount(SelectionSummary, {
mocks: { mocks: {
...@@ -45,33 +50,21 @@ describe('Selection Summary component', () => { ...@@ -45,33 +50,21 @@ describe('Selection Summary component', () => {
wrapper = null; wrapper = null;
}); });
describe('when vulnerabilities are selected', () => { describe('with 1 vulnerability selected', () => {
describe('it renders correctly', () => { beforeEach(() => {
beforeEach(() => { createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } }); });
});
it('returns the right message for one selected vulnerabilities', () => {
expect(dismissMessage().text()).toBe('Dismiss 1 selected vulnerability as');
});
it('returns the right message for greater than one selected vulnerabilities', () => { it('renders correctly', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] } }); expect(dismissMessage().text()).toBe('Dismiss 1 selected vulnerability as');
expect(dismissMessage().text()).toBe('Dismiss 2 selected vulnerabilities as');
});
}); });
describe('dismiss button', () => { describe('dismiss button', () => {
beforeEach(() => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
});
it('should have the button disabled if an option is not selected', () => { it('should have the button disabled if an option is not selected', () => {
expect(dismissButton().attributes('disabled')).toBe('disabled'); expect(dismissButton().attributes('disabled')).toBe('disabled');
}); });
it('should have the button enabled if a vulnerability is selected and an option is selected', () => { it('should have the button enabled if a vulnerability is selected and an option is selected', () => {
createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }] } });
expect(wrapper.vm.dismissalReason).toBe(null); expect(wrapper.vm.dismissalReason).toBe(null);
expect(wrapper.findAll('option')).toHaveLength(4); expect(wrapper.findAll('option')).toHaveLength(4);
formSelect() formSelect()
...@@ -85,40 +78,69 @@ describe('Selection Summary component', () => { ...@@ -85,40 +78,69 @@ describe('Selection Summary component', () => {
}); });
}); });
}); });
});
describe('clicking the dismiss vulnerability button', () => { describe('with 1 vulnerabilities selected', () => {
beforeEach(() => { beforeEach(() => {
createComponent({ createComponent({ props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] } });
props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] }, });
data: { dismissalReason: 'Will Not Fix' },
}); it('renders correctly', () => {
expect(dismissMessage().text()).toBe('Dismiss 2 selected vulnerabilities as');
});
});
describe('clicking the dismiss vulnerability button', () => {
let mutateMock;
beforeEach(() => {
mutateMock = jest.fn().mockResolvedValue();
createComponent({
props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] },
data: { dismissalReason: 'Will Not Fix' },
mocks: { $apollo: { mutate: mutateMock } },
});
});
it('should make an API request for each vulnerability', () => {
dismissButton().trigger('submit');
expect(spyMutate).toHaveBeenCalledTimes(2);
});
it('should show toast with the right message if all calls were successful', () => {
dismissButton().trigger('submit');
return waitForPromises().then(() => {
expect(toast).toHaveBeenCalledWith('2 vulnerabilities dismissed');
}); });
});
it('should make an API request for each vulnerability', () => { it('should show flash with the right message if some calls failed', () => {
dismissButton().trigger('submit'); mutateMock.mockRejectedValue();
expect(spyMutate).toHaveBeenCalledTimes(2); dismissButton().trigger('submit');
return waitForPromises().then(() => {
expect(createFlash).toHaveBeenCalledWith(
'There was an error dismissing the vulnerabilities.',
'alert',
);
}); });
});
it('should show toast with the right message if all calls were successful', () => { it('should emit an event to refetch the vulnerabilities when the request is successful', () => {
dismissButton().trigger('submit'); dismissButton().trigger('submit');
setImmediate(() => { return waitForPromises().then(() => {
expect(toast).toHaveBeenCalledWith('2 vulnerabilities dismissed'); expect(wrapper.emittedByOrder()).toEqual([
}); { name: 'deselect-all-vulnerabilities', args: [] },
{ name: 'refetch-vulnerabilities', args: [] },
]);
}); });
});
it('should show flash with the right message if some calls failed', () => { it('should still emit an event to refetch the vulnerabilities when the request fails', () => {
createComponent({ mutateMock.mockRejectedValue();
props: { selectedVulnerabilities: [{ id: 'id_0' }, { id: 'id_1' }] }, dismissButton().trigger('submit');
data: { dismissalReason: 'Will Not Fix' }, return waitForPromises().then(() => {
mocks: { $apollo: { mutate: jest.fn().mockRejectedValue() } }, expect(wrapper.emittedByOrder()).toEqual([{ name: 'refetch-vulnerabilities', args: [] }]);
});
dismissButton().trigger('submit');
setImmediate(() => {
expect(createFlash).toHaveBeenCalledWith(
'There was an error dismissing the vulnerabilities.',
'alert',
);
});
}); });
}); });
}); });
...@@ -127,8 +149,9 @@ describe('Selection Summary component', () => { ...@@ -127,8 +149,9 @@ describe('Selection Summary component', () => {
beforeEach(() => { beforeEach(() => {
createComponent({}); createComponent({});
}); });
it('should have the button disabled', () => { it('should have the button disabled', () => {
expect(dismissButton().attributes().disabled).toBe('disabled'); expect(dismissButton().attributes('disabled')).toBe('disabled');
}); });
}); });
}); });
...@@ -77,25 +77,37 @@ describe('Vulnerability list component', () => { ...@@ -77,25 +77,37 @@ describe('Vulnerability list component', () => {
expect(findCheckAllCheckboxCell().classes()).toContain('d-none'); expect(findCheckAllCheckboxCell().classes()).toContain('d-none');
expect(findFirstCheckboxCell().classes()).toContain('d-none'); expect(findFirstCheckboxCell().classes()).toContain('d-none');
}); });
});
it('should show the selection summary when a checkbox is selected', () => { describe('when vulnerability selection is enabled', () => {
beforeEach(() => {
wrapper = createWrapper({ wrapper = createWrapper({
props: { vulnerabilities, shouldShowSelection: true }, props: { vulnerabilities, shouldShowSelection: true },
}); });
const checkbox = findFirstCheckboxCell().find('input');
checkbox.setChecked(); findFirstCheckboxCell()
.find('input')
.setChecked(true);
});
it('should show the selection summary when a checkbox is selected', () => {
return wrapper.vm.$nextTick().then(() => { return wrapper.vm.$nextTick().then(() => {
expect(findSelectionSummary().exists()).toBe(true); expect(findSelectionSummary().exists()).toBe(true);
}); });
}); });
it('should show the checkboxes if shouldShowSelection is passed in', () => { it('should show the checkboxes if shouldShowSelection is passed in', () => {
wrapper = createWrapper({
props: { vulnerabilities, shouldShowSelection: true },
});
expect(findCheckAllCheckboxCell().classes()).not.toContain('d-none'); expect(findCheckAllCheckboxCell().classes()).not.toContain('d-none');
expect(findFirstCheckboxCell().classes()).not.toContain('d-none'); expect(findFirstCheckboxCell().classes()).not.toContain('d-none');
}); });
it('should sync selected vulnerabilities when the vulnerability list is updated', () => {
expect(findSelectionSummary().props('selectedVulnerabilities')).toHaveLength(1);
wrapper.setProps({ vulnerabilities: [] });
return wrapper.vm.$nextTick().then(() => {
expect(findSelectionSummary().exists()).toBe(false);
});
});
}); });
describe('when displayed on instance or group level dashboard', () => { describe('when displayed on instance or group level dashboard', () => {
......
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