Commit 9db77bf6 authored by David O'Regan's avatar David O'Regan

Merge branch '328385-deduplicate-specs-2' into 'master'

Deduplicate ManageViaMr specs [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!61886
parents 98cd2a52 65b0ce17
...@@ -16,6 +16,16 @@ export default { ...@@ -16,6 +16,16 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
variant: {
type: String,
required: false,
default: 'success',
},
category: {
type: String,
required: false,
default: 'secondary',
},
}, },
data() { data() {
return { return {
...@@ -65,8 +75,8 @@ export default { ...@@ -65,8 +75,8 @@ export default {
<gl-button <gl-button
v-if="!feature.configured" v-if="!feature.configured"
:loading="isLoading" :loading="isLoading"
variant="success" :variant="variant"
category="secondary" :category="category"
@click="mutate" @click="mutate"
>{{ $options.i18n.buttonLabel }}</gl-button >{{ $options.i18n.buttonLabel }}</gl-button
> >
......
...@@ -67,7 +67,7 @@ describe('ConfigurationTable component', () => { ...@@ -67,7 +67,7 @@ describe('ConfigurationTable component', () => {
gitlabCiHistoryPath: propsData.gitlabCiHistoryPath, gitlabCiHistoryPath: propsData.gitlabCiHistoryPath,
autoDevopsEnabled: propsData.autoDevopsEnabled, autoDevopsEnabled: propsData.autoDevopsEnabled,
}); });
expect(manage.find(ManageFeature).props()).toEqual({ feature }); expect(manage.find(ManageFeature).props()).toMatchObject({ feature });
expect(description.find(GlLink).attributes('href')).toBe(feature.helpPath); expect(description.find(GlLink).attributes('href')).toBe(feature.helpPath);
}); });
......
...@@ -73,7 +73,7 @@ describe('ManageFeature component', () => { ...@@ -73,7 +73,7 @@ describe('ManageFeature component', () => {
}); });
it('passes through props to expected component', () => { it('passes through props to expected component', () => {
expect(component.props()).toEqual({ feature }); expect(component.props()).toMatchObject({ feature });
}); });
}); });
......
import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
import configureDependencyScanningMutation from 'ee/security_configuration/graphql/configure_dependency_scanning.mutation.graphql';
import configureSecretDetectionMutation from 'ee/security_configuration/graphql/configure_secret_detection.mutation.graphql';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { buildConfigureSecurityFeatureMockFactory } from 'jest/vue_shared/security_reports/components/apollo_mocks';
import { redirectTo } from '~/lib/utils/url_utility';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo);
const projectPath = 'namespace/project';
describe('ManageViaMr component', () => {
let wrapper;
const findButton = () => wrapper.findComponent(GlButton);
describe.each`
featureName | featureType | mutation | mutationId
${'SECRET_DETECTION'} | ${REPORT_TYPE_DEPENDENCY_SCANNING} | ${configureDependencyScanningMutation} | ${'configureDependencyScanning'}
${'DEPENDENCY_SCANNING'} | ${REPORT_TYPE_SECRET_DETECTION} | ${configureSecretDetectionMutation} | ${'configureSecretDetection'}
`('$featureType', ({ featureName, mutation, featureType, mutationId }) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(mutationId);
const successHandler = jest.fn(async () => buildConfigureSecurityFeatureMock());
const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({
successPath: '',
});
const errorHandler = async () =>
buildConfigureSecurityFeatureMock({
errors: ['foo'],
});
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) {
const requestHandlers = [[mutation, handler]];
return createMockApollo(requestHandlers);
}
function createComponent({ mockApollo, isFeatureConfigured = false } = {}) {
wrapper = extendedWrapper(
mount(ManageViaMr, {
apolloProvider: mockApollo,
provide: {
projectPath,
},
propsData: {
feature: {
name: featureName,
type: featureType,
configured: isFeatureConfigured,
},
},
}),
);
}
afterEach(() => {
wrapper.destroy();
});
describe('when feature is configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: true });
});
it('it does not render a button', () => {
expect(findButton().exists()).toBe(false);
});
});
describe('when feature is not configured', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo, isFeatureConfigured: false });
});
it('it does render a button', () => {
expect(findButton().exists()).toBe(true);
});
it('clicking on the button triggers the configure mutation', () => {
findButton().trigger('click');
expect(successHandler).toHaveBeenCalledTimes(1);
expect(successHandler).toHaveBeenCalledWith({
input: {
projectPath,
},
});
});
});
describe('given a pending response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => {
const button = findButton();
expect(button.props('loading')).toBe(false);
await button.trigger('click');
expect(button.props('loading')).toBe(true);
});
});
describe('given a successful response', () => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler);
createComponent({ mockApollo });
});
it('should call redirect helper with correct value', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(redirectTo).toHaveBeenCalledTimes(1);
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
});
describe.each`
handler | message
${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`}
${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => {
beforeEach(() => {
const mockApollo = createMockApolloProvider(handler);
createComponent({ mockApollo });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
});
});
});
...@@ -2,138 +2,182 @@ import { GlButton } from '@gitlab/ui'; ...@@ -2,138 +2,182 @@ import { GlButton } from '@gitlab/ui';
import { mount } from '@vue/test-utils'; import { mount } from '@vue/test-utils';
import Vue from 'vue'; import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import { featureToMutationMap } from 'ee_else_ce/security_configuration/components/constants';
import createMockApollo from 'helpers/mock_apollo_helper'; import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { humanize } from '~/lib/utils/text_utility';
import { redirectTo } from '~/lib/utils/url_utility'; import { redirectTo } from '~/lib/utils/url_utility';
import configureSast from '~/security_configuration/graphql/configure_sast.mutation.graphql';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue'; import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks'; import { buildConfigureSecurityFeatureMockFactory } from './apollo_mocks';
jest.mock('~/lib/utils/url_utility'); jest.mock('~/lib/utils/url_utility');
Vue.use(VueApollo); Vue.use(VueApollo);
const projectPath = 'namespace/project';
describe('ManageViaMr component', () => { describe('ManageViaMr component', () => {
let wrapper; let wrapper;
const findButton = () => wrapper.findComponent(GlButton); const findButton = () => wrapper.findComponent(GlButton);
describe.each`
featureName | featureType | mutation | mutationId
${'SAST'} | ${REPORT_TYPE_SAST} | ${configureSast} | ${'configureSast'}
`('$featureType', ({ featureName, mutation, featureType, mutationId }) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(mutationId);
const successHandler = async () => buildConfigureSecurityFeatureMock();
const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({
successPath: '',
});
const errorHandler = async () =>
buildConfigureSecurityFeatureMock({
errors: ['foo'],
});
const pendingHandler = () => new Promise(() => {});
function createMockApolloProvider(handler) { function createMockApolloProvider(mutation, handler) {
const requestHandlers = [[mutation, handler]]; const requestHandlers = [[mutation, handler]];
return createMockApollo(requestHandlers); return createMockApollo(requestHandlers);
} }
function createComponent({ mockApollo, isFeatureConfigured = false } = {}) { function createComponent({
wrapper = extendedWrapper( featureName = 'SAST',
mount(ManageViaMr, { featureType = 'sast',
apolloProvider: mockApollo, isFeatureConfigured = false,
provide: { variant = undefined,
projectPath: 'testProjectPath', category = undefined,
}, ...options
propsData: { } = {}) {
feature: { wrapper = extendedWrapper(
name: featureName, mount(ManageViaMr, {
type: featureType, provide: {
configured: isFeatureConfigured, projectPath,
}, },
propsData: {
feature: {
name: featureName,
type: featureType,
configured: isFeatureConfigured,
}, },
}), variant,
); category,
} },
...options,
afterEach(() => { }),
wrapper.destroy(); );
}); }
describe('when feature is configured', () => { afterEach(() => {
beforeEach(() => { wrapper.destroy();
const mockApollo = createMockApolloProvider(successHandler); });
createComponent({ mockApollo, isFeatureConfigured: true });
});
it('it does not render a button', () => { // This component supports different report types/mutations depending on
expect(findButton().exists()).toBe(false); // whether it's in a CE or EE context. This makes sure we are only testing
// the ones available in the current test context.
const supportedReportTypes = Object.entries(featureToMutationMap).map(
([featureType, { getMutationPayload, mutationId }]) => {
const { mutation, variables: mutationVariables } = getMutationPayload(projectPath);
return [humanize(featureType), featureType, mutation, mutationId, mutationVariables];
},
);
describe.each(supportedReportTypes)(
'%s',
(featureName, featureType, mutation, mutationId, mutationVariables) => {
const buildConfigureSecurityFeatureMock = buildConfigureSecurityFeatureMockFactory(
mutationId,
);
const successHandler = jest.fn(async () => buildConfigureSecurityFeatureMock());
const noSuccessPathHandler = async () =>
buildConfigureSecurityFeatureMock({
successPath: '',
});
const errorHandler = async () =>
buildConfigureSecurityFeatureMock({
errors: ['foo'],
});
const pendingHandler = () => new Promise(() => {});
describe('when feature is configured', () => {
beforeEach(() => {
const apolloProvider = createMockApolloProvider(mutation, successHandler);
createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: true });
});
it('it does not render a button', () => {
expect(findButton().exists()).toBe(false);
});
}); });
});
describe('when feature is not configured', () => { describe('when feature is not configured', () => {
beforeEach(() => { beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler); const apolloProvider = createMockApolloProvider(mutation, successHandler);
createComponent({ mockApollo, isFeatureConfigured: false }); createComponent({ apolloProvider, featureName, featureType, isFeatureConfigured: false });
}); });
it('it does render a button', () => { it('it does render a button', () => {
expect(findButton().exists()).toBe(true); expect(findButton().exists()).toBe(true);
}); });
});
describe('given a pending response', () => { it('clicking on the button triggers the configure mutation', () => {
beforeEach(() => { findButton().trigger('click');
const mockApollo = createMockApolloProvider(pendingHandler);
createComponent({ mockApollo });
});
it('renders spinner correctly', async () => { expect(successHandler).toHaveBeenCalledTimes(1);
const button = findButton(); expect(successHandler).toHaveBeenCalledWith(mutationVariables);
expect(button.props('loading')).toBe(false); });
await button.trigger('click');
expect(button.props('loading')).toBe(true);
}); });
});
describe('given a successful response', () => { describe('given a pending response', () => {
beforeEach(() => { beforeEach(() => {
const mockApollo = createMockApolloProvider(successHandler); const apolloProvider = createMockApolloProvider(mutation, pendingHandler);
createComponent({ mockApollo }); createComponent({ apolloProvider, featureName, featureType });
});
it('renders spinner correctly', async () => {
const button = findButton();
expect(button.props('loading')).toBe(false);
await button.trigger('click');
expect(button.props('loading')).toBe(true);
});
}); });
it('should call redirect helper with correct value', async () => { describe('given a successful response', () => {
await wrapper.trigger('click'); beforeEach(() => {
await waitForPromises(); const apolloProvider = createMockApolloProvider(mutation, successHandler);
expect(redirectTo).toHaveBeenCalledTimes(1); createComponent({ apolloProvider, featureName, featureType });
expect(redirectTo).toHaveBeenCalledWith('testSuccessPath'); });
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable it('should call redirect helper with correct value', async () => {
// again. Instead, we want the button to display a loading indicator await wrapper.trigger('click');
// for the remainder of the lifetime of the page (i.e., until the await waitForPromises();
// browser can start painting the new page it's been redirected to). expect(redirectTo).toHaveBeenCalledTimes(1);
expect(findButton().props().loading).toBe(true); expect(redirectTo).toHaveBeenCalledWith('testSuccessPath');
// This is done for UX reasons. If the loading prop is set to false
// on success, then there's a period where the button is clickable
// again. Instead, we want the button to display a loading indicator
// for the remainder of the lifetime of the page (i.e., until the
// browser can start painting the new page it's been redirected to).
expect(findButton().props().loading).toBe(true);
});
}); });
});
describe.each` describe.each`
handler | message handler | message
${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`} ${noSuccessPathHandler} | ${`${featureName} merge request creation mutation failed`}
${errorHandler} | ${'foo'} ${errorHandler} | ${'foo'}
`('given an error response', ({ handler, message }) => { `('given an error response', ({ handler, message }) => {
beforeEach(() => { beforeEach(() => {
const mockApollo = createMockApolloProvider(handler); const apolloProvider = createMockApolloProvider(mutation, handler);
createComponent({ mockApollo }); createComponent({ apolloProvider, featureName, featureType });
});
it('should catch and emit error', async () => {
await wrapper.trigger('click');
await waitForPromises();
expect(wrapper.emitted('error')).toEqual([[message]]);
expect(findButton().props('loading')).toBe(false);
});
}); });
},
it('should catch and emit error', async () => { );
await wrapper.trigger('click');
await waitForPromises(); describe('button props', () => {
expect(wrapper.emitted('error')).toEqual([[message]]); it('passes the variant and category props to the GlButton', () => {
expect(findButton().props('loading')).toBe(false); const variant = 'danger';
const category = 'tertiary';
createComponent({ variant, category });
expect(wrapper.findComponent(GlButton).props()).toMatchObject({
variant,
category,
}); });
}); });
}); });
......
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