Commit 23ad7e55 authored by Dave Pisek's avatar Dave Pisek

Vulnerability Reports: simplify adding components

* Small refactor to reduce the amount of code needed when
  adding a new component for rendering a report-type
* Add missing cleanups to specs
parent ac7a6af6
<script> <script>
import List from './types/list.vue'; import { REPORT_COMPONENTS, getComponentNameForType } from './types/constants';
import Url from './types/url.vue';
export default { export default {
components: { components: {
List, ...REPORT_COMPONENTS,
Url,
}, },
props: { props: {
item: { item: {
...@@ -13,8 +11,13 @@ export default { ...@@ -13,8 +11,13 @@ export default {
required: true, required: true,
}, },
}, },
computed: {
componentName() {
return getComponentNameForType(this.item.type);
},
},
}; };
</script> </script>
<template> <template>
<component :is="item.type" v-bind="item" data-testid="reportComponent" /> <component :is="componentName" v-bind="item" data-testid="reportComponent" />
</template> </template>
export const REPORT_TYPE_LIST = 'list'; import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
export const REPORT_TYPE_URL = 'url';
export const REPORT_TYPES = [REPORT_TYPE_LIST, REPORT_TYPE_URL]; export const REPORT_TYPES = {
list: 'list',
url: 'url',
};
const REPORT_TYPE_TO_COMPONENT_MAP = {
[REPORT_TYPES.list]: () => import('./list.vue'),
[REPORT_TYPES.url]: () => import('./url.vue'),
};
export const getComponentNameForType = (reportType) =>
`ReportType${capitalizeFirstCharacter(reportType)}`;
export const REPORT_COMPONENTS = Object.fromEntries(
Object.entries(REPORT_TYPE_TO_COMPONENT_MAP).map(([reportType, component]) => [
getComponentNameForType(reportType),
component,
]),
);
...@@ -27,7 +27,7 @@ export default { ...@@ -27,7 +27,7 @@ export default {
:key="item.name" :key="item.name"
:class="{ 'gl-list-style-none!': $options.isListType(item) }" :class="{ 'gl-list-style-none!': $options.isListType(item) }"
> >
<report-item :item="item" /> <report-item :item="item" data-testid="reportItem" />
</li> </li>
</ul> </ul>
</template> </template>
import { overEvery } from 'lodash'; import { overEvery } from 'lodash';
import { REPORT_TYPES, REPORT_TYPE_LIST } from './constants'; import { REPORT_TYPES } from './constants';
/** /**
* Check if the given report is of a type that can be rendered (i.e, is mapped to a component and can be rendered) * Check if the given report is of a type that can be rendered (i.e, is mapped to a component and can be rendered)
...@@ -7,7 +7,7 @@ import { REPORT_TYPES, REPORT_TYPE_LIST } from './constants'; ...@@ -7,7 +7,7 @@ import { REPORT_TYPES, REPORT_TYPE_LIST } from './constants';
* @param {{ type: string }} reportItem * @param {{ type: string }} reportItem
* @returns boolean * @returns boolean
*/ */
const isSupportedType = ({ type }) => REPORT_TYPES.includes(type); const isSupportedType = ({ type }) => Object.values(REPORT_TYPES).includes(type);
/** /**
* Check if the given report is of type list * Check if the given report is of type list
...@@ -15,7 +15,7 @@ const isSupportedType = ({ type }) => REPORT_TYPES.includes(type); ...@@ -15,7 +15,7 @@ const isSupportedType = ({ type }) => REPORT_TYPES.includes(type);
* @param {{ type: string } } reportItem * @param {{ type: string } } reportItem
* @returns boolean * @returns boolean
*/ */
export const isListType = ({ type }) => type === REPORT_TYPE_LIST; export const isListType = ({ type }) => type === REPORT_TYPES.list;
/** /**
* Check if the current report item is of that list and is not nested deeper than the maximum depth * Check if the current report item is of that list and is not nested deeper than the maximum depth
......
...@@ -2,17 +2,16 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,17 +2,16 @@ import { shallowMount } from '@vue/test-utils';
import ReportItem from 'ee/vulnerabilities/components/generic_report/report_item.vue'; import ReportItem from 'ee/vulnerabilities/components/generic_report/report_item.vue';
import { import {
REPORT_TYPES, REPORT_TYPES,
REPORT_TYPE_URL, REPORT_COMPONENTS,
REPORT_TYPE_LIST,
} from 'ee/vulnerabilities/components/generic_report/types/constants'; } from 'ee/vulnerabilities/components/generic_report/types/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
const TEST_DATA = { const TEST_DATA = {
[REPORT_TYPE_URL]: { [REPORT_TYPES.url]: {
href: 'http://foo.com', href: 'http://foo.com',
}, },
[REPORT_TYPE_LIST]: { [REPORT_TYPES.list]: {
items: [{ type: 'foo' }], items: [],
}, },
}; };
...@@ -26,12 +25,18 @@ describe('ee/vulnerabilities/components/generic_report/report_item.vue', () => { ...@@ -26,12 +25,18 @@ describe('ee/vulnerabilities/components/generic_report/report_item.vue', () => {
item: {}, item: {},
...props, ...props,
}, },
// manual stubbing is needed because the components are dynamically imported
stubs: Object.keys(REPORT_COMPONENTS),
}), }),
); );
const findReportComponent = () => wrapper.findByTestId('reportComponent'); const findReportComponent = () => wrapper.findByTestId('reportComponent');
describe.each(REPORT_TYPES)('with report type "%s"', (reportType) => { afterEach(() => {
wrapper.destroy();
});
describe.each(Object.values(REPORT_TYPES))('with report type "%s"', (reportType) => {
const reportItem = { type: reportType, ...TEST_DATA[reportType] }; const reportItem = { type: reportType, ...TEST_DATA[reportType] };
beforeEach(() => { beforeEach(() => {
......
...@@ -2,19 +2,19 @@ import { within, fireEvent } from '@testing-library/dom'; ...@@ -2,19 +2,19 @@ import { within, fireEvent } from '@testing-library/dom';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import ReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue'; import ReportSection from 'ee/vulnerabilities/components/generic_report/report_section.vue';
import { REPORT_TYPE_URL } from 'ee/vulnerabilities/components/generic_report/types/constants'; import { REPORT_TYPES } from 'ee/vulnerabilities/components/generic_report/types/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
const TEST_DATA = { const TEST_DATA = {
supportedTypes: { supportedTypes: {
one: { one: {
name: 'one', name: 'one',
type: REPORT_TYPE_URL, type: REPORT_TYPES.url,
href: 'http://foo.com', href: 'http://foo.com',
}, },
two: { two: {
name: 'two', name: 'two',
type: REPORT_TYPE_URL, type: REPORT_TYPES.url,
href: 'http://bar.com', href: 'http://bar.com',
}, },
}, },
......
import { screen } from '@testing-library/dom'; import { screen } from '@testing-library/dom';
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import ReportItem from 'ee/vulnerabilities/components/generic_report/report_item.vue';
import List from 'ee/vulnerabilities/components/generic_report/types/list.vue'; import List from 'ee/vulnerabilities/components/generic_report/types/list.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
const TEST_DATA = { const TEST_DATA = {
items: [ items: [
...@@ -13,21 +13,30 @@ const TEST_DATA = { ...@@ -13,21 +13,30 @@ const TEST_DATA = {
describe('ee/vulnerabilities/components/generic_report/types/list.vue', () => { describe('ee/vulnerabilities/components/generic_report/types/list.vue', () => {
let wrapper; let wrapper;
const createWrapper = () => { const createWrapper = () =>
return shallowMount(List, { extendedWrapper(
shallowMount(List, {
propsData: { propsData: {
items: TEST_DATA.items, items: TEST_DATA.items,
}, },
attachTo: document.body, attachTo: document.body,
}); // manual stubbing is needed because the component is dynamically imported
}; stubs: {
ReportItem: true,
},
}),
);
const findReportItems = () => wrapper.findAllComponents(ReportItem); const findReportItems = () => wrapper.findAllByTestId('reportItem');
beforeEach(() => { beforeEach(() => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
afterEach(() => {
wrapper.destroy();
});
it('renders a list', () => { it('renders a list', () => {
expect(screen.getByRole('list')).toBeInstanceOf(HTMLElement); expect(screen.getByRole('list')).toBeInstanceOf(HTMLElement);
}); });
......
...@@ -23,6 +23,10 @@ describe('ee/vulnerabilities/components/generic_report/types/url.vue', () => { ...@@ -23,6 +23,10 @@ describe('ee/vulnerabilities/components/generic_report/types/url.vue', () => {
wrapper = createWrapper(); wrapper = createWrapper();
}); });
afterEach(() => {
wrapper.destroy();
});
it('renders a link', () => { it('renders a link', () => {
expect(findLink().exists()).toBe(true); expect(findLink().exists()).toBe(true);
}); });
......
import { import { REPORT_TYPES } from 'ee/vulnerabilities/components/generic_report/types/constants';
REPORT_TYPE_LIST,
REPORT_TYPE_URL,
} from 'ee/vulnerabilities/components/generic_report/types/constants';
import { filterTypesAndLimitListDepth } from 'ee/vulnerabilities/components/generic_report/types/utils'; import { filterTypesAndLimitListDepth } from 'ee/vulnerabilities/components/generic_report/types/utils';
const MOCK_REPORT_TYPE_UNSUPPORTED = 'MOCK_REPORT_TYPE_UNSUPPORTED'; const MOCK_REPORT_TYPE_UNSUPPORTED = 'MOCK_REPORT_TYPE_UNSUPPORTED';
const TEST_DATA = { const TEST_DATA = {
url: { url: {
type: REPORT_TYPE_URL, type: REPORT_TYPES.url,
name: 'url1', name: 'url1',
}, },
list: { list: {
type: REPORT_TYPE_LIST, type: REPORT_TYPES.list,
name: 'rootList', name: 'rootList',
items: [ items: [
{ type: REPORT_TYPE_URL, name: 'url2' }, { type: REPORT_TYPES.url, name: 'url2' },
{ {
type: REPORT_TYPE_LIST, type: REPORT_TYPES.list,
name: 'listDepthOne', name: 'listDepthOne',
items: [ items: [
{ type: REPORT_TYPE_URL, name: 'url3' }, { type: REPORT_TYPES.url, name: 'url3' },
{ {
type: REPORT_TYPE_LIST, type: REPORT_TYPES.list,
name: 'listDepthTwo', name: 'listDepthTwo',
items: [ items: [
{ type: REPORT_TYPE_URL, name: 'url4' }, { type: REPORT_TYPES.url, name: 'url4' },
{ {
type: REPORT_TYPE_LIST, type: REPORT_TYPES.list,
name: 'listDepthThree', name: 'listDepthThree',
items: [ items: [
{ type: REPORT_TYPE_URL, name: 'url5' }, { type: REPORT_TYPES.url, name: 'url5' },
{ type: MOCK_REPORT_TYPE_UNSUPPORTED }, { type: MOCK_REPORT_TYPE_UNSUPPORTED },
], ],
}, },
...@@ -52,7 +49,7 @@ describe('ee/vulnerabilities/components/generic_report/types/utils', () => { ...@@ -52,7 +49,7 @@ describe('ee/vulnerabilities/components/generic_report/types/utils', () => {
const getListWithDepthTwo = (reportsData) => reportsData.list.items[1].items[1]; const getListWithDepthTwo = (reportsData) => reportsData.list.items[1].items[1];
const includesType = (type) => (items) => const includesType = (type) => (items) =>
items.find(({ type: currentType }) => currentType === type) !== undefined; items.find(({ type: currentType }) => currentType === type) !== undefined;
const includesListItem = includesType(REPORT_TYPE_LIST); const includesListItem = includesType(REPORT_TYPES.list);
const includesUnsupportedType = includesType(MOCK_REPORT_TYPE_UNSUPPORTED); const includesUnsupportedType = includesType(MOCK_REPORT_TYPE_UNSUPPORTED);
describe.each` describe.each`
......
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