summaryrefslogtreecommitdiff
path: root/spec/frontend/security_configuration/components/training_provider_list_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/security_configuration/components/training_provider_list_spec.js')
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js189
1 files changed, 159 insertions, 30 deletions
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 18c9ada6bde..b8c1bef0ddd 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -1,15 +1,20 @@
import * as Sentry from '@sentry/browser';
-import { GlAlert, GlLink, GlToggle, GlCard, GlSkeletonLoader } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlAlert, GlLink, GlToggle, GlCard, GlSkeletonLoader, GlIcon } from '@gitlab/ui';
import Vue from 'vue';
import VueApollo from 'vue-apollo';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import createMockApollo from 'helpers/mock_apollo_helper';
import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
+import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import {
TRACK_TOGGLE_TRAINING_PROVIDER_ACTION,
TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
} from '~/security_configuration/constants';
+import { TEMP_PROVIDER_URLS } from '~/security_configuration/components/constants';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
+import { updateSecurityTrainingOptimisticResponse } from '~/security_configuration/graphql/cache_utils';
import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql';
import configureSecurityTrainingProvidersMutation from '~/security_configuration/graphql/configure_security_training_providers.mutation.graphql';
import dismissUserCalloutMutation from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
@@ -17,16 +22,30 @@ import waitForPromises from 'helpers/wait_for_promises';
import {
dismissUserCalloutResponse,
dismissUserCalloutErrorResponse,
- securityTrainingProviders,
- securityTrainingProvidersResponse,
+ getSecurityTrainingProvidersData,
updateSecurityTrainingProvidersResponse,
updateSecurityTrainingProvidersErrorResponse,
testProjectPath,
- textProviderIds,
+ testProviderIds,
+ testProviderName,
+ tempProviderLogos,
} from '../mock_data';
Vue.use(VueApollo);
+const TEST_TRAINING_PROVIDERS_ALL_DISABLED = getSecurityTrainingProvidersData();
+const TEST_TRAINING_PROVIDERS_FIRST_ENABLED = getSecurityTrainingProvidersData({
+ providerOverrides: { first: { isEnabled: true, isPrimary: true } },
+});
+const TEST_TRAINING_PROVIDERS_ALL_ENABLED = getSecurityTrainingProvidersData({
+ providerOverrides: {
+ first: { isEnabled: true, isPrimary: true },
+ second: { isEnabled: true, isPrimary: false },
+ third: { isEnabled: true, isPrimary: false },
+ },
+});
+const TEST_TRAINING_PROVIDERS_DEFAULT = TEST_TRAINING_PROVIDERS_ALL_DISABLED;
+
describe('TrainingProviderList component', () => {
let wrapper;
let apolloProvider;
@@ -35,7 +54,7 @@ describe('TrainingProviderList component', () => {
const defaultHandlers = [
[
securityTrainingProvidersQuery,
- jest.fn().mockResolvedValue(securityTrainingProvidersResponse),
+ jest.fn().mockResolvedValue(TEST_TRAINING_PROVIDERS_DEFAULT.response),
],
[
configureSecurityTrainingProvidersMutation,
@@ -50,10 +69,13 @@ describe('TrainingProviderList component', () => {
};
const createComponent = () => {
- wrapper = shallowMount(TrainingProviderList, {
+ wrapper = shallowMountExtended(TrainingProviderList, {
provide: {
projectFullPath: testProjectPath,
},
+ directives: {
+ GlTooltip: createMockDirective(),
+ },
apolloProvider,
});
};
@@ -65,10 +87,12 @@ describe('TrainingProviderList component', () => {
const findLinks = () => wrapper.findAllComponents(GlLink);
const findToggles = () => wrapper.findAllComponents(GlToggle);
const findFirstToggle = () => findToggles().at(0);
+ const findPrimaryProviderRadios = () => wrapper.findAllByTestId('primary-provider-radio');
const findLoader = () => wrapper.findComponent(GlSkeletonLoader);
const findErrorAlert = () => wrapper.findComponent(GlAlert);
+ const findLogos = () => wrapper.findAllByTestId('provider-logo');
- const toggleFirstProvider = () => findFirstToggle().vm.$emit('change', textProviderIds[0]);
+ const toggleFirstProvider = () => findFirstToggle().vm.$emit('change', testProviderIds[0]);
afterEach(() => {
wrapper.destroy();
@@ -104,7 +128,7 @@ describe('TrainingProviderList component', () => {
Mutation: {
configureSecurityTrainingProviders: () => ({
errors: [],
- securityTrainingProviders: [],
+ TEST_TRAINING_PROVIDERS_DEFAULT: [],
}),
},
},
@@ -119,10 +143,10 @@ describe('TrainingProviderList component', () => {
});
it('renders correct amount of cards', () => {
- expect(findCards()).toHaveLength(securityTrainingProviders.length);
+ expect(findCards()).toHaveLength(TEST_TRAINING_PROVIDERS_DEFAULT.data.length);
});
- securityTrainingProviders.forEach(({ name, description, url, isEnabled }, index) => {
+ TEST_TRAINING_PROVIDERS_DEFAULT.data.forEach(({ name, description, isEnabled }, index) => {
it(`shows the name for card ${index}`, () => {
expect(findCards().at(index).text()).toContain(name);
});
@@ -131,23 +155,76 @@ describe('TrainingProviderList component', () => {
expect(findCards().at(index).text()).toContain(description);
});
- it(`shows the learn more link for card ${index}`, () => {
- expect(findLinks().at(index).attributes()).toEqual({
- target: '_blank',
- href: url,
- });
+ it(`shows the learn more link for enabled card ${index}`, () => {
+ const learnMoreLink = findCards().at(index).find(GlLink);
+ const tempLogo = TEMP_PROVIDER_URLS[name];
+
+ if (tempLogo) {
+ expect(learnMoreLink.attributes()).toEqual({
+ target: '_blank',
+ href: TEMP_PROVIDER_URLS[name],
+ });
+ } else {
+ expect(learnMoreLink.exists()).toBe(false);
+ }
});
it(`shows the toggle with the correct value for card ${index}`, () => {
expect(findToggles().at(index).props('value')).toEqual(isEnabled);
});
+ it(`shows a radio button to select the provider as primary within card ${index}`, () => {
+ const primaryProviderRadioForCurrentCard = findPrimaryProviderRadios().at(index);
+
+ // if the given provider is not enabled it should not be possible select it as primary
+ expect(primaryProviderRadioForCurrentCard.find('input').attributes('disabled')).toBe(
+ isEnabled ? undefined : 'disabled',
+ );
+
+ expect(primaryProviderRadioForCurrentCard.text()).toBe(
+ TrainingProviderList.i18n.primaryTraining,
+ );
+ });
+
+ it('shows a info-tooltip that describes the purpose of a primary provider', () => {
+ const infoIcon = findPrimaryProviderRadios().at(index).find(GlIcon);
+ const tooltip = getBinding(infoIcon.element, 'gl-tooltip');
+
+ expect(infoIcon.props()).toMatchObject({
+ name: 'information-o',
+ });
+ expect(tooltip.value).toBe(TrainingProviderList.i18n.primaryTrainingDescription);
+ });
+
it('does not show loader when query is populated', () => {
expect(findLoader().exists()).toBe(false);
});
});
});
+ describe('provider logo', () => {
+ beforeEach(async () => {
+ wrapper.vm.$options.TEMP_PROVIDER_LOGOS = tempProviderLogos;
+ await waitForQueryToBeLoaded();
+ });
+
+ const providerIndexArray = [0, 1];
+
+ it.each(providerIndexArray)('displays the correct width for provider %s', (provider) => {
+ expect(findLogos().at(provider).attributes('style')).toBe('width: 18px;');
+ });
+
+ it.each(providerIndexArray)('has a11y decorative attribute for provider %s', (provider) => {
+ expect(findLogos().at(provider).attributes('role')).toBe('presentation');
+ });
+
+ it.each(providerIndexArray)('renders the svg content for provider %s', (provider) => {
+ expect(findLogos().at(provider).html()).toContain(
+ tempProviderLogos[testProviderName[provider]].svg,
+ );
+ });
+ });
+
describe('storing training provider settings', () => {
beforeEach(async () => {
jest.spyOn(apolloProvider.defaultClient, 'mutate');
@@ -157,26 +234,15 @@ describe('TrainingProviderList component', () => {
await toggleFirstProvider();
});
- it.each`
- loading | wait | desc
- ${true} | ${false} | ${'enables loading of GlToggle when mutation is called'}
- ${false} | ${true} | ${'disables loading of GlToggle when mutation is complete'}
- `('$desc', async ({ loading, wait }) => {
- if (wait) {
- await waitForMutationToBeLoaded();
- }
- expect(findFirstToggle().props('isLoading')).toBe(loading);
- });
-
it('calls mutation when toggle is changed', () => {
expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith(
expect.objectContaining({
mutation: configureSecurityTrainingProvidersMutation,
variables: {
input: {
- providerId: textProviderIds[0],
+ providerId: testProviderIds[0],
isEnabled: true,
- isPrimary: false,
+ isPrimary: true,
projectPath: testProjectPath,
},
},
@@ -184,6 +250,20 @@ describe('TrainingProviderList component', () => {
);
});
+ it('returns an optimistic response when calling the mutation', () => {
+ const optimisticResponse = updateSecurityTrainingOptimisticResponse({
+ id: TEST_TRAINING_PROVIDERS_DEFAULT.data[0].id,
+ isEnabled: true,
+ isPrimary: true,
+ });
+
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenCalledWith(
+ expect.objectContaining({
+ optimisticResponse,
+ }),
+ );
+ });
+
it('dismisses the callout when the feature gets first enabled', async () => {
// wait for configuration update mutation to complete
await waitForMutationToBeLoaded();
@@ -237,13 +317,62 @@ describe('TrainingProviderList component', () => {
// Once https://gitlab.com/gitlab-org/gitlab/-/issues/348985 and https://gitlab.com/gitlab-org/gitlab/-/merge_requests/79492
// are merged this will be much easer to do and should be tackled then.
expect(trackingSpy).toHaveBeenCalledWith(undefined, TRACK_TOGGLE_TRAINING_PROVIDER_ACTION, {
- property: securityTrainingProviders[0].id,
+ property: TEST_TRAINING_PROVIDERS_DEFAULT.data[0].id,
label: TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
extra: {
providerIsEnabled: true,
},
});
});
+
+ it(`tracks when a provider's "Learn more" link is clicked`, () => {
+ const firstProviderLink = findLinks().at(0);
+ const [{ id: firstProviderId }] = TEST_TRAINING_PROVIDERS_DEFAULT.data;
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ firstProviderLink.vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ undefined,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
+ {
+ label: TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
+ property: firstProviderId,
+ },
+ );
+ });
+ });
+ });
+
+ describe('primary provider settings', () => {
+ it.each`
+ description | initialProviderData | expectedMutationInput
+ ${'sets the provider to be non-primary when it gets disabled'} | ${TEST_TRAINING_PROVIDERS_FIRST_ENABLED.response} | ${{ providerId: TEST_TRAINING_PROVIDERS_FIRST_ENABLED.data[0].id, isEnabled: false, isPrimary: false }}
+ ${'sets a provider to be primary when it is the only one enabled'} | ${TEST_TRAINING_PROVIDERS_ALL_DISABLED.response} | ${{ providerId: TEST_TRAINING_PROVIDERS_ALL_DISABLED.data[0].id, isEnabled: true, isPrimary: true }}
+ ${'sets the first other enabled provider to be primary when the primary one gets disabled'} | ${TEST_TRAINING_PROVIDERS_ALL_ENABLED.response} | ${{ providerId: TEST_TRAINING_PROVIDERS_ALL_ENABLED.data[1].id, isEnabled: true, isPrimary: true }}
+ `('$description', async ({ initialProviderData, expectedMutationInput }) => {
+ createApolloProvider({
+ handlers: [
+ [securityTrainingProvidersQuery, jest.fn().mockResolvedValue(initialProviderData)],
+ ],
+ });
+ jest.spyOn(apolloProvider.defaultClient, 'mutate');
+ createComponent();
+
+ await waitForQueryToBeLoaded();
+ await toggleFirstProvider();
+
+ expect(apolloProvider.defaultClient.mutate).toHaveBeenNthCalledWith(
+ 1,
+ expect.objectContaining({
+ variables: {
+ input: expect.objectContaining({
+ ...expectedMutationInput,
+ }),
+ },
+ }),
+ );
});
});