diff options
Diffstat (limited to 'spec/frontend/ci_variable_list')
6 files changed, 255 insertions, 17 deletions
diff --git a/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js new file mode 100644 index 00000000000..867f8e0cf8f --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js @@ -0,0 +1,215 @@ +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import { GlLoadingIcon, GlTable } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; +import { resolvers } from '~/ci_variable_list/graphql/resolvers'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; + +import ciProjectVariables from '~/ci_variable_list/components/ci_project_variables.vue'; +import ciVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue'; +import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue'; +import getProjectEnvironments from '~/ci_variable_list/graphql/queries/project_environments.query.graphql'; +import getProjectVariables from '~/ci_variable_list/graphql/queries/project_variables.query.graphql'; + +import addProjectVariable from '~/ci_variable_list/graphql/mutations/project_add_variable.mutation.graphql'; +import deleteProjectVariable from '~/ci_variable_list/graphql/mutations/project_delete_variable.mutation.graphql'; +import updateProjectVariable from '~/ci_variable_list/graphql/mutations/project_update_variable.mutation.graphql'; + +import { + environmentFetchErrorText, + genericMutationErrorText, + variableFetchErrorText, +} from '~/ci_variable_list/constants'; + +import { + devName, + mockProjectEnvironments, + mockProjectVariables, + newVariable, + prodName, +} from '../mocks'; + +jest.mock('~/flash'); + +Vue.use(VueApollo); + +const mockProvide = { + endpoint: '/variables', + projectFullPath: '/namespace/project', + projectId: 1, +}; + +describe('Ci Project Variable list', () => { + let wrapper; + + let mockApollo; + let mockEnvironments; + let mockVariables; + + const findLoadingIcon = () => wrapper.findComponent(GlLoadingIcon); + const findCiTable = () => wrapper.findComponent(GlTable); + const findCiSettings = () => wrapper.findComponent(ciVariableSettings); + + // eslint-disable-next-line consistent-return + const createComponentWithApollo = async ({ isLoading = false } = {}) => { + const handlers = [ + [getProjectEnvironments, mockEnvironments], + [getProjectVariables, mockVariables], + ]; + + mockApollo = createMockApollo(handlers, resolvers); + + wrapper = shallowMount(ciProjectVariables, { + provide: mockProvide, + apolloProvider: mockApollo, + stubs: { ciVariableSettings, ciVariableTable }, + }); + + if (!isLoading) { + return waitForPromises(); + } + }; + + beforeEach(() => { + mockEnvironments = jest.fn(); + mockVariables = jest.fn(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('while queries are being fetch', () => { + beforeEach(() => { + createComponentWithApollo({ isLoading: true }); + }); + + it('shows a loading icon', () => { + expect(findLoadingIcon().exists()).toBe(true); + expect(findCiTable().exists()).toBe(false); + }); + }); + + describe('when queries are resolved', () => { + describe('successfuly', () => { + beforeEach(async () => { + mockEnvironments.mockResolvedValue(mockProjectEnvironments); + mockVariables.mockResolvedValue(mockProjectVariables); + + await createComponentWithApollo(); + }); + + it('passes down the expected environments as props', () => { + expect(findCiSettings().props('environments')).toEqual([prodName, devName]); + }); + + it('passes down the expected variables as props', () => { + expect(findCiSettings().props('variables')).toEqual( + mockProjectVariables.data.project.ciVariables.nodes, + ); + }); + + it('createFlash was not called', () => { + expect(createFlash).not.toHaveBeenCalled(); + }); + }); + + describe('with an error for variables', () => { + beforeEach(async () => { + mockEnvironments.mockResolvedValue(mockProjectEnvironments); + mockVariables.mockRejectedValue(); + + await createComponentWithApollo(); + }); + + it('calls createFlash with the expected error message', () => { + expect(createFlash).toHaveBeenCalledWith({ message: variableFetchErrorText }); + }); + }); + + describe('with an error for environments', () => { + beforeEach(async () => { + mockEnvironments.mockRejectedValue(); + mockVariables.mockResolvedValue(mockProjectVariables); + + await createComponentWithApollo(); + }); + + it('calls createFlash with the expected error message', () => { + expect(createFlash).toHaveBeenCalledWith({ message: environmentFetchErrorText }); + }); + }); + }); + + describe('mutations', () => { + beforeEach(async () => { + mockEnvironments.mockResolvedValue(mockProjectEnvironments); + mockVariables.mockResolvedValue(mockProjectVariables); + + await createComponentWithApollo(); + }); + it.each` + actionName | mutation | event + ${'add'} | ${addProjectVariable} | ${'add-variable'} + ${'update'} | ${updateProjectVariable} | ${'update-variable'} + ${'delete'} | ${deleteProjectVariable} | ${'delete-variable'} + `( + 'calls the right mutation when user performs $actionName variable', + async ({ event, mutation }) => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + await findCiSettings().vm.$emit(event, newVariable); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation, + variables: { + endpoint: mockProvide.endpoint, + fullPath: mockProvide.projectFullPath, + projectId: convertToGraphQLId('Project', mockProvide.projectId), + variable: newVariable, + }, + }); + }, + ); + + it.each` + actionName | event | mutationName + ${'add'} | ${'add-variable'} | ${'addProjectVariable'} + ${'update'} | ${'update-variable'} | ${'updateProjectVariable'} + ${'delete'} | ${'delete-variable'} | ${'deleteProjectVariable'} + `( + 'throws with the specific graphql error if present when user performs $actionName variable', + async ({ event, mutationName }) => { + const graphQLErrorMessage = 'There is a problem with this graphQL action'; + jest + .spyOn(wrapper.vm.$apollo, 'mutate') + .mockResolvedValue({ data: { [mutationName]: { errors: [graphQLErrorMessage] } } }); + await findCiSettings().vm.$emit(event, newVariable); + await nextTick(); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ message: graphQLErrorMessage }); + }, + ); + + it.each` + actionName | event + ${'add'} | ${'add-variable'} + ${'update'} | ${'update-variable'} + ${'delete'} | ${'delete-variable'} + `( + 'throws generic error when the mutation fails with no graphql errors and user performs $actionName variable', + async ({ event }) => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockImplementationOnce(() => { + throw new Error(); + }); + await findCiSettings().vm.$emit(event, newVariable); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled(); + expect(createFlash).toHaveBeenCalledWith({ message: genericMutationErrorText }); + }, + ); + }); +}); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js index e5019e3261e..1ea4e4f833b 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_modal_spec.js @@ -11,6 +11,7 @@ import { EVENT_ACTION, ENVIRONMENT_SCOPE_LINK_TITLE, instanceString, + variableOptions, } from '~/ci_variable_list/constants'; import { mockVariablesWithScopes } from '../mocks'; import ModalStub from '../stubs'; @@ -57,21 +58,23 @@ describe('Ci variable modal', () => { }); }; - const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown); + const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown); const findReferenceWarning = () => wrapper.findByTestId('contains-variable-reference'); - const findModal = () => wrapper.find(ModalStub); + const findModal = () => wrapper.findComponent(ModalStub); const findAWSTip = () => wrapper.findByTestId('aws-guidance-tip'); const findAddorUpdateButton = () => wrapper.findByTestId('ciUpdateOrAddVariableBtn'); const deleteVariableButton = () => findModal() - .findAll(GlButton) + .findAllComponents(GlButton) .wrappers.find((button) => button.props('variant') === 'danger'); const findProtectedVariableCheckbox = () => wrapper.findByTestId('ci-variable-protected-checkbox'); const findMaskedVariableCheckbox = () => wrapper.findByTestId('ci-variable-masked-checkbox'); const findValueField = () => wrapper.find('#ci-variable-value'); const findEnvScopeLink = () => wrapper.findByTestId('environment-scope-link'); - const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').find(GlFormInput); + const findEnvScopeInput = () => + wrapper.findByTestId('environment-scope').findComponent(GlFormInput); + const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type'); afterEach(() => { wrapper.destroy(); @@ -83,7 +86,7 @@ describe('Ci variable modal', () => { createComponent(); }); - it('shows the submit button as disabled ', () => { + it('shows the submit button as disabled', () => { expect(findAddorUpdateButton().attributes('disabled')).toBe('true'); }); }); @@ -93,7 +96,7 @@ describe('Ci variable modal', () => { createComponent({ props: { selectedVariable: mockVariables[0] } }); }); - it('shows the submit button as enabled ', () => { + it('shows the submit button as enabled', () => { expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined(); }); }); @@ -284,6 +287,21 @@ describe('Ci variable modal', () => { }); }); + describe('variable type dropdown', () => { + describe('default behaviour', () => { + beforeEach(() => { + createComponent({ mountFn: mountExtended }); + }); + + it('adds each option as a dropdown item', () => { + expect(findVariableTypeDropdown().findAll('option')).toHaveLength(variableOptions.length); + variableOptions.forEach((v) => { + expect(findVariableTypeDropdown().text()).toContain(v.text); + }); + }); + }); + }); + describe('Validations', () => { const maskError = 'This variable can not be masked.'; diff --git a/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js index b43153d3d7c..4d0c378d10e 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js @@ -18,7 +18,7 @@ describe('Ci Variable Popover', () => { }); }; - const findButton = () => wrapper.find(GlButton); + const findButton = () => wrapper.findComponent(GlButton); beforeEach(() => { createComponent(); diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js index 6681ab91a4a..b607232907b 100644 --- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js +++ b/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js @@ -40,12 +40,12 @@ describe('Ci variable modal', () => { }); }; - const findCiEnvironmentsDropdown = () => wrapper.find(CiEnvironmentsDropdown); - const findModal = () => wrapper.find(ModalStub); + const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown); + const findModal = () => wrapper.findComponent(ModalStub); const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]'); const deleteVariableButton = () => findModal() - .findAll(GlButton) + .findAllComponents(GlButton) .wrappers.find((button) => button.props('variant') === 'danger'); afterEach(() => { @@ -213,7 +213,7 @@ describe('Ci variable modal', () => { const environmentScopeInput = wrapper .find('[data-testid="environment-scope"]') - .find(GlFormInput); + .findComponent(GlFormInput); expect(findCiEnvironmentsDropdown().exists()).toBe(false); expect(environmentScopeInput.attributes('readonly')).toBe('readonly'); }); diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js index 89ba77858dc..6d633c8b740 100644 --- a/spec/frontend/ci_variable_list/mocks.js +++ b/spec/frontend/ci_variable_list/mocks.js @@ -1,4 +1,9 @@ -import { variableTypes, groupString, instanceString } from '~/ci_variable_list/constants'; +import { + variableTypes, + groupString, + instanceString, + projectString, +} from '~/ci_variable_list/constants'; export const devName = 'dev'; export const prodName = 'prod'; @@ -11,8 +16,8 @@ export const mockVariables = (kind) => { key: 'my-var', masked: false, protected: true, - value: 'env_val', - variableType: variableTypes.variableType, + value: 'variable_value', + variableType: variableTypes.envType, }, { __typename: `Ci${kind}Variable`, @@ -20,7 +25,7 @@ export const mockVariables = (kind) => { key: 'secret', masked: true, protected: false, - value: 'the_secret_value', + value: 'another_value', variableType: variableTypes.fileType, }, ]; @@ -77,7 +82,7 @@ export const mockProjectVariables = { project: { __typename: 'Project', id: 1, - ciVariables: createDefaultVars(), + ciVariables: createDefaultVars({ kind: projectString }), }, }, }; diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js index ae750ff426d..c7d07ead09b 100644 --- a/spec/frontend/ci_variable_list/store/mutations_spec.js +++ b/spec/frontend/ci_variable_list/store/mutations_spec.js @@ -36,7 +36,7 @@ describe('CI variable list mutations', () => { }); describe('CLEAR_MODAL', () => { - it('should clear modal state ', () => { + it('should clear modal state', () => { const modalState = { variable_type: 'Variable', key: '', |