diff options
Diffstat (limited to 'spec/frontend/ci_variable_list')
16 files changed, 642 insertions, 1652 deletions
diff --git a/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js index 864041141b8..c7375acd8e5 100644 --- a/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_admin_variables_spec.js @@ -1,178 +1,35 @@ -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 { createAlert } from '~/flash'; -import { resolvers } from '~/ci_variable_list/graphql/settings'; import ciAdminVariables from '~/ci_variable_list/components/ci_admin_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 getAdminVariables from '~/ci_variable_list/graphql/queries/variables.query.graphql'; +import ciVariableShared from '~/ci_variable_list/components/ci_variable_shared.vue'; -import addAdminVariable from '~/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql'; -import deleteAdminVariable from '~/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql'; -import updateAdminVariable from '~/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql'; - -import { genericMutationErrorText, variableFetchErrorText } from '~/ci_variable_list/constants'; - -import { mockAdminVariables, newVariable } from '../mocks'; - -jest.mock('~/flash'); - -Vue.use(VueApollo); - -const mockProvide = { - endpoint: '/variables', -}; - -describe('Ci Admin Variable list', () => { +describe('Ci Project Variable wrapper', () => { let wrapper; - let mockApollo; - 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 = [[getAdminVariables, mockVariables]]; - - mockApollo = createMockApollo(handlers, resolvers); + const findCiShared = () => wrapper.findComponent(ciVariableShared); - wrapper = shallowMount(ciAdminVariables, { - provide: mockProvide, - apolloProvider: mockApollo, - stubs: { ciVariableSettings, ciVariableTable }, - }); - - if (!isLoading) { - return waitForPromises(); - } + const createComponent = () => { + wrapper = shallowMount(ciAdminVariables); }; beforeEach(() => { - mockVariables = jest.fn(); + createComponent(); }); 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); + it('Passes down the correct props to ci_variable_shared', () => { + expect(findCiShared().props()).toEqual({ + areScopedVariablesAvailable: false, + componentName: 'InstanceVariables', + hideEnvironmentScope: true, + mutationData: wrapper.vm.$options.mutationData, + queryData: wrapper.vm.$options.queryData, + refetchAfterMutation: true, + fullPath: null, + id: null, }); }); - - describe('when queries are resolved', () => { - describe('successfuly', () => { - beforeEach(async () => { - mockVariables.mockResolvedValue(mockAdminVariables); - - await createComponentWithApollo(); - }); - - it('passes down the expected environments as props', () => { - expect(findCiSettings().props('environments')).toEqual([]); - }); - - it('passes down the expected variables as props', () => { - expect(findCiSettings().props('variables')).toEqual( - mockAdminVariables.data.ciVariables.nodes, - ); - }); - - it('createAlert was not called', () => { - expect(createAlert).not.toHaveBeenCalled(); - }); - }); - - describe('with an error for variables', () => { - beforeEach(async () => { - mockVariables.mockRejectedValue(); - - await createComponentWithApollo(); - }); - - it('calls createAlert with the expected error message', () => { - expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText }); - }); - }); - }); - - describe('mutations', () => { - beforeEach(async () => { - mockVariables.mockResolvedValue(mockAdminVariables); - - await createComponentWithApollo(); - }); - it.each` - actionName | mutation | event - ${'add'} | ${addAdminVariable} | ${'add-variable'} - ${'update'} | ${updateAdminVariable} | ${'update-variable'} - ${'delete'} | ${deleteAdminVariable} | ${'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, - variable: newVariable, - }, - }); - }, - ); - - it.each` - actionName | event | mutationName - ${'add'} | ${'add-variable'} | ${'addAdminVariable'} - ${'update'} | ${'update-variable'} | ${'updateAdminVariable'} - ${'delete'} | ${'delete-variable'} | ${'deleteAdminVariable'} - `( - '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(createAlert).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(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText }); - }, - ); - }); }); diff --git a/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js index 8a48e73eb9f..ef5a86ccb61 100644 --- a/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_group_variables_spec.js @@ -1,183 +1,72 @@ -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 { createAlert } from '~/flash'; -import { resolvers } from '~/ci_variable_list/graphql/settings'; import { convertToGraphQLId } from '~/graphql_shared/utils'; import ciGroupVariables from '~/ci_variable_list/components/ci_group_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 getGroupVariables from '~/ci_variable_list/graphql/queries/group_variables.query.graphql'; +import ciVariableShared from '~/ci_variable_list/components/ci_variable_shared.vue'; -import addGroupVariable from '~/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql'; -import deleteGroupVariable from '~/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql'; -import updateGroupVariable from '~/ci_variable_list/graphql/mutations/group_update_variable.mutation.graphql'; - -import { genericMutationErrorText, variableFetchErrorText } from '~/ci_variable_list/constants'; - -import { mockGroupVariables, newVariable } from '../mocks'; - -jest.mock('~/flash'); - -Vue.use(VueApollo); +import { GRAPHQL_GROUP_TYPE } from '~/ci_variable_list/constants'; const mockProvide = { - endpoint: '/variables', - groupPath: '/namespace/group', - groupId: 1, + glFeatures: { + groupScopedCiVariables: false, + }, + groupPath: '/group', + groupId: 12, }; -describe('Ci Group Variable list', () => { +describe('Ci Group Variable wrapper', () => { let wrapper; - let mockApollo; - 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 = [[getGroupVariables, mockVariables]]; - - mockApollo = createMockApollo(handlers, resolvers); + const findCiShared = () => wrapper.findComponent(ciVariableShared); + const createComponent = ({ provide = {} } = {}) => { wrapper = shallowMount(ciGroupVariables, { - provide: mockProvide, - apolloProvider: mockApollo, - stubs: { ciVariableSettings, ciVariableTable }, + provide: { ...mockProvide, ...provide }, }); - - if (!isLoading) { - return waitForPromises(); - } }; - beforeEach(() => { - mockVariables = jest.fn(); - }); - afterEach(() => { wrapper.destroy(); }); - describe('while queries are being fetch', () => { + describe('Props', () => { beforeEach(() => { - createComponentWithApollo({ isLoading: true }); + createComponent(); }); - it('shows a loading icon', () => { - expect(findLoadingIcon().exists()).toBe(true); - expect(findCiTable().exists()).toBe(false); + it('are passed down the correctly to ci_variable_shared', () => { + expect(findCiShared().props()).toEqual({ + id: convertToGraphQLId(GRAPHQL_GROUP_TYPE, mockProvide.groupId), + areScopedVariablesAvailable: false, + componentName: 'GroupVariables', + fullPath: mockProvide.groupPath, + hideEnvironmentScope: false, + mutationData: wrapper.vm.$options.mutationData, + queryData: wrapper.vm.$options.queryData, + refetchAfterMutation: false, + }); }); }); - describe('when queries are resolved', () => { - describe('successfuly', () => { - beforeEach(async () => { - mockVariables.mockResolvedValue(mockGroupVariables); - - await createComponentWithApollo(); - }); - - it('passes down the expected environments as props', () => { - expect(findCiSettings().props('environments')).toEqual([]); - }); - - it('passes down the expected variables as props', () => { - expect(findCiSettings().props('variables')).toEqual( - mockGroupVariables.data.group.ciVariables.nodes, - ); + describe('feature flag', () => { + describe('When enabled', () => { + beforeEach(() => { + createComponent({ provide: { glFeatures: { groupScopedCiVariables: true } } }); }); - it('createAlert was not called', () => { - expect(createAlert).not.toHaveBeenCalled(); + it('Passes down `true` to variable shared component', () => { + expect(findCiShared().props('areScopedVariablesAvailable')).toBe(true); }); }); - describe('with an error for variables', () => { - beforeEach(async () => { - mockVariables.mockRejectedValue(); - - await createComponentWithApollo(); + describe('When disabled', () => { + beforeEach(() => { + createComponent({ provide: { glFeatures: { groupScopedCiVariables: false } } }); }); - it('calls createAlert with the expected error message', () => { - expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText }); + it('Passes down `false` to variable shared component', () => { + expect(findCiShared().props('areScopedVariablesAvailable')).toBe(false); }); }); }); - - describe('mutations', () => { - beforeEach(async () => { - mockVariables.mockResolvedValue(mockGroupVariables); - - await createComponentWithApollo(); - }); - it.each` - actionName | mutation | event - ${'add'} | ${addGroupVariable} | ${'add-variable'} - ${'update'} | ${updateGroupVariable} | ${'update-variable'} - ${'delete'} | ${deleteGroupVariable} | ${'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.groupPath, - groupId: convertToGraphQLId('Group', mockProvide.groupId), - variable: newVariable, - }, - }); - }, - ); - - it.each` - actionName | event | mutationName - ${'add'} | ${'add-variable'} | ${'addGroupVariable'} - ${'update'} | ${'update-variable'} | ${'updateGroupVariable'} - ${'delete'} | ${'delete-variable'} | ${'deleteGroupVariable'} - `( - '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(createAlert).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(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText }); - }, - ); - }); }); 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 index c630278fbde..97051325f59 100644 --- a/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_project_variables_spec.js @@ -1,215 +1,45 @@ -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 { createAlert } from '~/flash'; -import { resolvers } from '~/ci_variable_list/graphql/settings'; 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 ciVariableShared from '~/ci_variable_list/components/ci_variable_shared.vue'; -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); +import { GRAPHQL_PROJECT_TYPE } from '~/ci_variable_list/constants'; const mockProvide = { - endpoint: '/variables', projectFullPath: '/namespace/project', projectId: 1, }; -describe('Ci Project Variable list', () => { +describe('Ci Project Variable wrapper', () => { 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); + const findCiShared = () => wrapper.findComponent(ciVariableShared); + const createComponent = () => { wrapper = shallowMount(ciProjectVariables, { provide: mockProvide, - apolloProvider: mockApollo, - stubs: { ciVariableSettings, ciVariableTable }, }); - - if (!isLoading) { - return waitForPromises(); - } }; beforeEach(() => { - mockEnvironments = jest.fn(); - mockVariables = jest.fn(); + createComponent(); }); 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('createAlert was not called', () => { - expect(createAlert).not.toHaveBeenCalled(); - }); - }); - - describe('with an error for variables', () => { - beforeEach(async () => { - mockEnvironments.mockResolvedValue(mockProjectEnvironments); - mockVariables.mockRejectedValue(); - - await createComponentWithApollo(); - }); - - it('calls createAlert with the expected error message', () => { - expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText }); - }); - }); - - describe('with an error for environments', () => { - beforeEach(async () => { - mockEnvironments.mockRejectedValue(); - mockVariables.mockResolvedValue(mockProjectVariables); - - await createComponentWithApollo(); - }); - - it('calls createAlert with the expected error message', () => { - expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText }); - }); - }); - }); - - describe('mutations', () => { - beforeEach(async () => { - mockEnvironments.mockResolvedValue(mockProjectEnvironments); - mockVariables.mockResolvedValue(mockProjectVariables); - - await createComponentWithApollo(); + it('Passes down the correct props to ci_variable_shared', () => { + expect(findCiShared().props()).toEqual({ + id: convertToGraphQLId(GRAPHQL_PROJECT_TYPE, mockProvide.projectId), + areScopedVariablesAvailable: true, + componentName: 'ProjectVariables', + fullPath: mockProvide.projectFullPath, + hideEnvironmentScope: false, + mutationData: wrapper.vm.$options.mutationData, + queryData: wrapper.vm.$options.queryData, + refetchAfterMutation: false, }); - 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(createAlert).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(createAlert).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 1ea4e4f833b..e4771f040d1 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 @@ -39,6 +39,7 @@ describe('Ci variable modal', () => { const defaultProps = { areScopedVariablesAvailable: true, environments: [], + hideEnvironmentScope: false, mode: ADD_VARIABLE_ACTION, selectedVariable: {}, variable: [], @@ -75,6 +76,7 @@ describe('Ci variable modal', () => { const findEnvScopeInput = () => wrapper.findByTestId('environment-scope').findComponent(GlFormInput); const findVariableTypeDropdown = () => wrapper.find('#ci-variable-type'); + const findEnvironmentScopeText = () => wrapper.findByText('Environment scope'); afterEach(() => { wrapper.destroy(); @@ -250,39 +252,83 @@ describe('Ci variable modal', () => { describe('Environment scope', () => { describe('when feature is available', () => { - it('renders the environment dropdown', () => { - createComponent({ - mountFn: mountExtended, - props: { - areScopedVariablesAvailable: true, - }, + describe('and section is not hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: true, + hideEnvironmentScope: false, + }, + }); }); - expect(findCiEnvironmentsDropdown().exists()).toBe(true); - expect(findCiEnvironmentsDropdown().isVisible()).toBe(true); - }); + it('renders the environment dropdown and section title', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(true); + expect(findCiEnvironmentsDropdown().isVisible()).toBe(true); + expect(findEnvironmentScopeText().exists()).toBe(true); + }); - it('renders a link to documentation on scopes', () => { - createComponent({ mountFn: mountExtended }); + it('renders a link to documentation on scopes', () => { + const link = findEnvScopeLink(); + + expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); + expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink); + }); + }); - const link = findEnvScopeLink(); + describe('and section is hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: true, + hideEnvironmentScope: true, + }, + }); + }); - expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); - expect(link.attributes('href')).toBe(defaultProvide.environmentScopeLink); + it('does not renders the environment dropdown and section title', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + expect(findEnvironmentScopeText().exists()).toBe(false); + }); }); }); describe('when feature is not available', () => { - it('disables the dropdown', () => { - createComponent({ - mountFn: mountExtended, - props: { - areScopedVariablesAvailable: false, - }, + describe('and section is not hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: false, + hideEnvironmentScope: false, + }, + }); }); - expect(findCiEnvironmentsDropdown().exists()).toBe(false); - expect(findEnvScopeInput().attributes('readonly')).toBe('readonly'); + it('disables the dropdown', () => { + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + expect(findEnvironmentScopeText().exists()).toBe(true); + expect(findEnvScopeInput().attributes('readonly')).toBe('readonly'); + }); + }); + + describe('and section is hidden', () => { + beforeEach(() => { + createComponent({ + mountFn: mountExtended, + props: { + areScopedVariablesAvailable: false, + hideEnvironmentScope: true, + }, + }); + }); + + it('hides the dropdown', () => { + expect(findEnvironmentScopeText().exists()).toBe(false); + expect(findCiEnvironmentsDropdown().exists()).toBe(false); + }); }); }); }); 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 deleted file mode 100644 index 4d0c378d10e..00000000000 --- a/spec/frontend/ci_variable_list/components/ci_variable_popover_spec.js +++ /dev/null @@ -1,48 +0,0 @@ -import { GlButton } from '@gitlab/ui'; -import { shallowMount } from '@vue/test-utils'; -import CiVariablePopover from '~/ci_variable_list/components/ci_variable_popover.vue'; -import mockData from '../services/mock_data'; - -describe('Ci Variable Popover', () => { - let wrapper; - - const defaultProps = { - target: 'ci-variable-value-22', - value: mockData.mockPemCert, - tooltipText: 'Copy value', - }; - - const createComponent = (props = defaultProps) => { - wrapper = shallowMount(CiVariablePopover, { - propsData: { ...props }, - }); - }; - - const findButton = () => wrapper.findComponent(GlButton); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - it('displays max count plus ... when character count is over 95', () => { - expect(wrapper.text()).toHaveLength(98); - }); - - it('copies full value to clipboard', () => { - expect(findButton().attributes('data-clipboard-text')).toEqual(mockData.mockPemCert); - }); - - it('displays full value when count is less than max count', () => { - createComponent({ - target: 'ci-variable-value-22', - value: 'test_variable_value', - tooltipText: 'Copy value', - }); - expect(wrapper.text()).toEqual('test_variable_value'); - }); -}); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js index 5c77ce71b41..8b5a0f7ae9d 100644 --- a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js +++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js @@ -18,6 +18,7 @@ describe('Ci variable table', () => { const defaultProps = { areScopedVariablesAvailable: true, environments: mapEnvironmentNames(mockEnvs), + hideEnvironmentScope: false, isLoading: false, variables: mockVariablesWithScopes(projectString), }; @@ -56,6 +57,7 @@ describe('Ci variable table', () => { expect(findCiVariableModal().props()).toEqual({ areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable, environments: defaultProps.environments, + hideEnvironmentScope: defaultProps.hideEnvironmentScope, variables: defaultProps.variables, mode: ADD_VARIABLE_ACTION, selectedVariable: {}, diff --git a/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js new file mode 100644 index 00000000000..0cc0ee7a9c7 --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_variable_shared_spec.js @@ -0,0 +1,428 @@ +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 { createAlert } from '~/flash'; +import { resolvers } from '~/ci_variable_list/graphql/settings'; +import { convertToGraphQLId } from '~/graphql_shared/utils'; + +import ciVariableShared from '~/ci_variable_list/components/ci_variable_shared.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 getAdminVariables from '~/ci_variable_list/graphql/queries/variables.query.graphql'; +import getGroupVariables from '~/ci_variable_list/graphql/queries/group_variables.query.graphql'; +import getProjectVariables from '~/ci_variable_list/graphql/queries/project_variables.query.graphql'; + +import { + ADD_MUTATION_ACTION, + DELETE_MUTATION_ACTION, + UPDATE_MUTATION_ACTION, + environmentFetchErrorText, + genericMutationErrorText, + variableFetchErrorText, +} from '~/ci_variable_list/constants'; + +import { + createGroupProps, + createInstanceProps, + createProjectProps, + devName, + mockProjectEnvironments, + mockProjectVariables, + newVariable, + prodName, + mockGroupVariables, + mockAdminVariables, +} from '../mocks'; + +jest.mock('~/flash'); + +Vue.use(VueApollo); + +const mockProvide = { + endpoint: '/variables', +}; + +const defaultProps = { + areScopedVariablesAvailable: true, + hideEnvironmentScope: false, + refetchAfterMutation: false, +}; + +describe('Ci Variable Shared Component', () => { + 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 + async function createComponentWithApollo({ + customHandlers = null, + isLoading = false, + props = { ...createProjectProps() }, + } = {}) { + const handlers = customHandlers || [ + [getProjectEnvironments, mockEnvironments], + [getProjectVariables, mockVariables], + ]; + + mockApollo = createMockApollo(handlers, resolvers); + + wrapper = shallowMount(ciVariableShared, { + propsData: { + ...defaultProps, + ...props, + }, + provide: mockProvide, + apolloProvider: mockApollo, + stubs: { ciVariableSettings, ciVariableTable }, + }); + + if (!isLoading) { + return waitForPromises(); + } + } + + beforeEach(() => { + mockEnvironments = jest.fn(); + mockVariables = jest.fn(); + }); + + 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('createAlert was not called', () => { + expect(createAlert).not.toHaveBeenCalled(); + }); + }); + + describe('with an error for variables', () => { + beforeEach(async () => { + mockEnvironments.mockResolvedValue(mockProjectEnvironments); + mockVariables.mockRejectedValue(); + + await createComponentWithApollo(); + }); + + it('calls createAlert with the expected error message', () => { + expect(createAlert).toHaveBeenCalledWith({ message: variableFetchErrorText }); + }); + }); + + describe('with an error for environments', () => { + beforeEach(async () => { + mockEnvironments.mockRejectedValue(); + mockVariables.mockResolvedValue(mockProjectVariables); + + await createComponentWithApollo(); + }); + + it('calls createAlert with the expected error message', () => { + expect(createAlert).toHaveBeenCalledWith({ message: environmentFetchErrorText }); + }); + }); + }); + + describe('environment query', () => { + describe('when there is an environment key in queryData', () => { + beforeEach(async () => { + mockEnvironments.mockResolvedValue(mockProjectEnvironments); + mockVariables.mockResolvedValue(mockProjectVariables); + + await createComponentWithApollo({ props: { ...createProjectProps() } }); + }); + + it('is executed', () => { + expect(mockVariables).toHaveBeenCalled(); + }); + }); + + describe('when there isnt an environment key in queryData', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + + await createComponentWithApollo({ props: { ...createGroupProps() } }); + }); + + it('is skipped', () => { + expect(mockVariables).not.toHaveBeenCalled(); + }); + }); + }); + + describe('mutations', () => { + const groupProps = createGroupProps(); + + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + + await createComponentWithApollo({ + customHandlers: [[getGroupVariables, mockVariables]], + props: groupProps, + }); + }); + it.each` + actionName | mutation | event + ${'add'} | ${groupProps.mutationData[ADD_MUTATION_ACTION]} | ${'add-variable'} + ${'update'} | ${groupProps.mutationData[UPDATE_MUTATION_ACTION]} | ${'update-variable'} + ${'delete'} | ${groupProps.mutationData[DELETE_MUTATION_ACTION]} | ${'delete-variable'} + `( + 'calls the right mutation from propsData 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: groupProps.fullPath, + id: convertToGraphQLId('Group', groupProps.id), + variable: newVariable, + }, + }); + }, + ); + + it.each` + actionName | event + ${'add'} | ${'add-variable'} + ${'update'} | ${'update-variable'} + ${'delete'} | ${'delete-variable'} + `( + 'throws with the specific graphql error if present when user performs $actionName variable', + async ({ event }) => { + const graphQLErrorMessage = 'There is a problem with this graphQL action'; + jest + .spyOn(wrapper.vm.$apollo, 'mutate') + .mockResolvedValue({ data: { ciVariableMutation: { errors: [graphQLErrorMessage] } } }); + await findCiSettings().vm.$emit(event, newVariable); + await nextTick(); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalled(); + expect(createAlert).toHaveBeenCalledWith({ message: graphQLErrorMessage }); + }, + ); + + it.each` + actionName | event + ${'add'} | ${'add-variable'} + ${'update'} | ${'update-variable'} + ${'delete'} | ${'delete-variable'} + `( + 'throws generic error on failure 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(createAlert).toHaveBeenCalledWith({ message: genericMutationErrorText }); + }, + ); + + describe('without fullpath and ID props', () => { + beforeEach(async () => { + mockVariables.mockResolvedValue(mockAdminVariables); + + await createComponentWithApollo({ + customHandlers: [[getAdminVariables, mockVariables]], + props: createInstanceProps(), + }); + }); + + it('does not pass fullPath and ID to the mutation', async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue(); + + await findCiSettings().vm.$emit('add-variable', newVariable); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: wrapper.props().mutationData[ADD_MUTATION_ACTION], + variables: { + endpoint: mockProvide.endpoint, + variable: newVariable, + }, + }); + }); + }); + }); + + describe('Props', () => { + describe('in a specific context as', () => { + it.each` + name | mockVariablesValue | mockEnvironmentsValue | withEnvironments | expectedEnvironments | propsFn | mutation + ${'project'} | ${mockProjectVariables} | ${mockProjectEnvironments} | ${true} | ${['prod', 'dev']} | ${createProjectProps} | ${null} + ${'group'} | ${mockGroupVariables} | ${[]} | ${false} | ${[]} | ${createGroupProps} | ${getGroupVariables} + ${'instance'} | ${mockAdminVariables} | ${[]} | ${false} | ${[]} | ${createInstanceProps} | ${getAdminVariables} + `( + 'passes down all the required props when its a $name component', + async ({ + mutation, + mockVariablesValue, + mockEnvironmentsValue, + withEnvironments, + expectedEnvironments, + propsFn, + }) => { + const props = propsFn(); + + mockVariables.mockResolvedValue(mockVariablesValue); + + if (withEnvironments) { + mockEnvironments.mockResolvedValue(mockEnvironmentsValue); + } + + let customHandlers = null; + + if (mutation) { + customHandlers = [[mutation, mockVariables]]; + } + + await createComponentWithApollo({ customHandlers, props }); + + expect(findCiSettings().props()).toEqual({ + areScopedVariablesAvailable: wrapper.props().areScopedVariablesAvailable, + hideEnvironmentScope: defaultProps.hideEnvironmentScope, + isLoading: false, + variables: wrapper.props().queryData.ciVariables.lookup(mockVariablesValue.data)?.nodes, + environments: expectedEnvironments, + }); + }, + ); + }); + + describe('refetchAfterMutation', () => { + it.each` + bool | text + ${true} | ${'refetches the variables'} + ${false} | ${'does not refetch the variables'} + `('when $bool it $text', async ({ bool }) => { + await createComponentWithApollo({ + props: { ...createInstanceProps(), refetchAfterMutation: bool }, + }); + + jest.spyOn(wrapper.vm.$apollo, 'mutate').mockResolvedValue({ data: {} }); + jest.spyOn(wrapper.vm.$apollo.queries.ciVariables, 'refetch').mockImplementation(jest.fn()); + + await findCiSettings().vm.$emit('add-variable', newVariable); + + await nextTick(); + + if (bool) { + expect(wrapper.vm.$apollo.queries.ciVariables.refetch).toHaveBeenCalled(); + } else { + expect(wrapper.vm.$apollo.queries.ciVariables.refetch).not.toHaveBeenCalled(); + } + }); + }); + + describe('Validators', () => { + describe('queryData', () => { + let error; + + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + }); + + it('will mount component with right data', async () => { + try { + await createComponentWithApollo({ + customHandlers: [[getGroupVariables, mockVariables]], + props: { ...createGroupProps() }, + }); + } catch (e) { + error = e; + } finally { + expect(wrapper.exists()).toBe(true); + expect(error).toBeUndefined(); + } + }); + + it('will not mount component with wrong data', async () => { + try { + await createComponentWithApollo({ + customHandlers: [[getGroupVariables, mockVariables]], + props: { ...createGroupProps(), queryData: { wrongKey: {} } }, + }); + } catch (e) { + error = e; + } finally { + expect(wrapper.exists()).toBe(false); + expect(error.toString()).toContain('custom validator check failed for prop'); + } + }); + }); + + describe('mutationData', () => { + let error; + + beforeEach(async () => { + mockVariables.mockResolvedValue(mockGroupVariables); + }); + + it('will mount component with right data', async () => { + try { + await createComponentWithApollo({ + props: { ...createGroupProps() }, + }); + } catch (e) { + error = e; + } finally { + expect(wrapper.exists()).toBe(true); + expect(error).toBeUndefined(); + } + }); + + it('will not mount component with wrong data', async () => { + try { + await createComponentWithApollo({ + props: { ...createGroupProps(), mutationData: { wrongKey: {} } }, + }); + } catch (e) { + error = e; + } finally { + expect(wrapper.exists()).toBe(false); + expect(error.toString()).toContain('custom validator check failed for prop'); + } + }); + }); + }); + }); +}); diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js deleted file mode 100644 index b3e23ba4201..00000000000 --- a/spec/frontend/ci_variable_list/components/legacy_ci_environments_dropdown_spec.js +++ /dev/null @@ -1,119 +0,0 @@ -import { GlDropdown, GlDropdownItem, GlIcon } from '@gitlab/ui'; -import { mount } from '@vue/test-utils'; -import Vue, { nextTick } from 'vue'; -import Vuex from 'vuex'; -import LegacyCiEnvironmentsDropdown from '~/ci_variable_list/components/legacy_ci_environments_dropdown.vue'; - -Vue.use(Vuex); - -describe('Ci environments dropdown', () => { - let wrapper; - let store; - - const enterSearchTerm = (value) => - wrapper.find('[data-testid="ci-environment-search"]').setValue(value); - - const createComponent = (term) => { - store = new Vuex.Store({ - getters: { - joinedEnvironments: () => ['dev', 'prod', 'staging'], - }, - }); - - wrapper = mount(LegacyCiEnvironmentsDropdown, { - store, - propsData: { - value: term, - }, - }); - enterSearchTerm(term); - }; - - const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem); - const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index); - const findActiveIconByIndex = (index) => findDropdownItemByIndex(index).findComponent(GlIcon); - - afterEach(() => { - wrapper.destroy(); - wrapper = null; - }); - - describe('No environments found', () => { - beforeEach(() => { - createComponent('stable'); - }); - - it('renders create button with search term if environments do not contain search term', () => { - expect(findAllDropdownItems()).toHaveLength(2); - expect(findDropdownItemByIndex(1).text()).toBe('Create wildcard: stable'); - }); - - it('renders empty results message', () => { - expect(findDropdownItemByIndex(0).text()).toBe('No matching results'); - }); - }); - - describe('Search term is empty', () => { - beforeEach(() => { - createComponent(''); - }); - - it('renders all environments when search term is empty', () => { - expect(findAllDropdownItems()).toHaveLength(3); - expect(findDropdownItemByIndex(0).text()).toBe('dev'); - expect(findDropdownItemByIndex(1).text()).toBe('prod'); - expect(findDropdownItemByIndex(2).text()).toBe('staging'); - }); - - it('should not display active checkmark on the inactive stage', () => { - expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(true); - }); - }); - - describe('Environments found', () => { - beforeEach(async () => { - createComponent('prod'); - await nextTick(); - }); - - it('renders only the environment searched for', () => { - expect(findAllDropdownItems()).toHaveLength(1); - expect(findDropdownItemByIndex(0).text()).toBe('prod'); - }); - - it('should not display create button', () => { - const environments = findAllDropdownItems().filter((env) => env.text().startsWith('Create')); - expect(environments).toHaveLength(0); - expect(findAllDropdownItems()).toHaveLength(1); - }); - - it('should not display empty results message', () => { - expect(wrapper.findComponent({ ref: 'noMatchingResults' }).exists()).toBe(false); - }); - - it('should display active checkmark if active', () => { - expect(findActiveIconByIndex(0).classes('gl-visibility-hidden')).toBe(false); - }); - - it('should clear the search term when showing the dropdown', () => { - wrapper.findComponent(GlDropdown).trigger('click'); - - expect(wrapper.find('[data-testid="ci-environment-search"]').text()).toBe(''); - }); - - describe('Custom events', () => { - it('should emit selectEnvironment if an environment is clicked', () => { - findDropdownItemByIndex(0).vm.$emit('click'); - expect(wrapper.emitted('selectEnvironment')).toEqual([['prod']]); - }); - - it('should emit createClicked if an environment is clicked', async () => { - createComponent('newscope'); - - await nextTick(); - findDropdownItemByIndex(1).vm.$emit('click'); - expect(wrapper.emitted('createClicked')).toEqual([['newscope']]); - }); - }); - }); -}); 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 deleted file mode 100644 index b607232907b..00000000000 --- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_modal_spec.js +++ /dev/null @@ -1,323 +0,0 @@ -import { GlButton, GlFormInput } from '@gitlab/ui'; -import { shallowMount, mount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; -import { mockTracking } from 'helpers/tracking_helper'; -import CiEnvironmentsDropdown from '~/ci_variable_list/components/ci_environments_dropdown.vue'; -import LegacyCiVariableModal from '~/ci_variable_list/components/legacy_ci_variable_modal.vue'; -import { - AWS_ACCESS_KEY_ID, - EVENT_LABEL, - EVENT_ACTION, - ENVIRONMENT_SCOPE_LINK_TITLE, -} from '~/ci_variable_list/constants'; -import createStore from '~/ci_variable_list/store'; -import mockData from '../services/mock_data'; -import ModalStub from '../stubs'; - -Vue.use(Vuex); - -describe('Ci variable modal', () => { - let wrapper; - let store; - let trackingSpy; - - const maskableRegex = '^[a-zA-Z0-9_+=/@:.~-]{8,}$'; - - const createComponent = (method, options = {}) => { - store = createStore({ - maskableRegex, - isGroup: options.isGroup, - environmentScopeLink: '/help/environments', - }); - wrapper = method(LegacyCiVariableModal, { - attachTo: document.body, - stubs: { - GlModal: ModalStub, - }, - store, - ...options, - }); - }; - - const findCiEnvironmentsDropdown = () => wrapper.findComponent(CiEnvironmentsDropdown); - const findModal = () => wrapper.findComponent(ModalStub); - const findAddorUpdateButton = () => findModal().find('[data-testid="ciUpdateOrAddVariableBtn"]'); - const deleteVariableButton = () => - findModal() - .findAllComponents(GlButton) - .wrappers.find((button) => button.props('variant') === 'danger'); - - afterEach(() => { - wrapper.destroy(); - }); - - describe('Basic interactions', () => { - beforeEach(() => { - createComponent(shallowMount); - }); - - it('button is disabled when no key/value pair are present', () => { - expect(findAddorUpdateButton().attributes('disabled')).toBe('true'); - }); - }); - - describe('Adding a new variable', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - createComponent(shallowMount); - jest.spyOn(store, 'dispatch').mockImplementation(); - store.state.variable = variable; - }); - - it('button is enabled when key/value pair are present', () => { - expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined(); - }); - - it('Add variable button dispatches addVariable action', () => { - findAddorUpdateButton().vm.$emit('click'); - expect(store.dispatch).toHaveBeenCalledWith('addVariable'); - }); - - it('Clears the modal state once modal is hidden', () => { - findModal().vm.$emit('hidden'); - expect(store.dispatch).toHaveBeenCalledWith('clearModal'); - }); - - it('should dispatch setVariableProtected when admin settings are configured to protect variables', () => { - store.state.isProtectedByDefault = true; - findModal().vm.$emit('shown'); - - expect(store.dispatch).toHaveBeenCalledWith('setVariableProtected'); - }); - }); - - describe('Adding a new non-AWS variable', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const invalidKeyVariable = { - ...variable, - key: 'key', - value: 'value', - secret_value: 'secret_value', - }; - createComponent(mount); - store.state.variable = invalidKeyVariable; - }); - - it('does not show AWS guidance tip', () => { - const tip = wrapper.find(`div[data-testid='aws-guidance-tip']`); - expect(tip.exists()).toBe(true); - expect(tip.isVisible()).toBe(false); - }); - }); - - describe('Adding a new AWS variable', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const invalidKeyVariable = { - ...variable, - key: AWS_ACCESS_KEY_ID, - value: 'AKIAIOSFODNN7EXAMPLEjdhy', - secret_value: 'AKIAIOSFODNN7EXAMPLEjdhy', - }; - createComponent(mount); - store.state.variable = invalidKeyVariable; - }); - - it('shows AWS guidance tip', () => { - const tip = wrapper.find(`[data-testid='aws-guidance-tip']`); - expect(tip.exists()).toBe(true); - expect(tip.isVisible()).toBe(true); - }); - }); - - describe.each` - value | secret | rendered - ${'value'} | ${'secret_value'} | ${false} - ${'dollar$ign'} | ${'dollar$ign'} | ${true} - `('Adding a new variable', ({ value, secret, rendered }) => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const invalidKeyVariable = { - ...variable, - key: 'key', - value, - secret_value: secret, - }; - createComponent(mount); - store.state.variable = invalidKeyVariable; - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - it(`${rendered ? 'renders' : 'does not render'} the variable reference warning`, () => { - const warning = wrapper.find(`[data-testid='contains-variable-reference']`); - expect(warning.exists()).toBe(rendered); - }); - }); - - describe('Editing a variable', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - createComponent(shallowMount); - jest.spyOn(store, 'dispatch').mockImplementation(); - store.state.variableBeingEdited = variable; - }); - - it('button text is Update variable when updating', () => { - expect(findAddorUpdateButton().text()).toBe('Update variable'); - }); - - it('Update variable button dispatches updateVariable with correct variable', () => { - findAddorUpdateButton().vm.$emit('click'); - expect(store.dispatch).toHaveBeenCalledWith('updateVariable'); - }); - - it('Resets the editing state once modal is hidden', () => { - findModal().vm.$emit('hidden'); - expect(store.dispatch).toHaveBeenCalledWith('resetEditing'); - }); - - it('dispatches deleteVariable with correct variable to delete', () => { - deleteVariableButton().vm.$emit('click'); - expect(store.dispatch).toHaveBeenCalledWith('deleteVariable'); - }); - }); - - describe('Environment scope', () => { - describe('group level variables', () => { - it('renders the environment dropdown', () => { - createComponent(shallowMount, { - isGroup: true, - provide: { - glFeatures: { - groupScopedCiVariables: true, - }, - }, - }); - - expect(findCiEnvironmentsDropdown().exists()).toBe(true); - expect(findCiEnvironmentsDropdown().isVisible()).toBe(true); - }); - - describe('licensed feature is not available', () => { - it('disables the dropdown', () => { - createComponent(mount, { - isGroup: true, - provide: { - glFeatures: { - groupScopedCiVariables: false, - }, - }, - }); - - const environmentScopeInput = wrapper - .find('[data-testid="environment-scope"]') - .findComponent(GlFormInput); - expect(findCiEnvironmentsDropdown().exists()).toBe(false); - expect(environmentScopeInput.attributes('readonly')).toBe('readonly'); - }); - }); - }); - - it('renders a link to documentation on scopes', () => { - createComponent(mount); - - const link = wrapper.find('[data-testid="environment-scope-link"]'); - - expect(link.attributes('title')).toBe(ENVIRONMENT_SCOPE_LINK_TITLE); - expect(link.attributes('href')).toBe('/help/environments'); - }); - }); - - describe('Validations', () => { - const maskError = 'This variable can not be masked.'; - - describe('when the mask state is invalid', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const invalidMaskVariable = { - ...variable, - key: 'qs', - value: 'd:;', - secret_value: 'd:;', - masked: true, - }; - createComponent(mount); - store.state.variable = invalidMaskVariable; - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - it('disables the submit button', () => { - expect(findAddorUpdateButton().attributes('disabled')).toBe('disabled'); - }); - - it('shows the correct error text', () => { - expect(findModal().text()).toContain(maskError); - }); - - it('sends the correct tracking event', () => { - expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, { - label: EVENT_LABEL, - property: ';', - }); - }); - }); - - describe.each` - value | secret | masked | eventSent | trackingErrorProperty - ${'value'} | ${'secretValue'} | ${false} | ${0} | ${null} - ${'shortMasked'} | ${'short'} | ${true} | ${0} | ${null} - ${'withDollar$Sign'} | ${'dollar$ign'} | ${false} | ${1} | ${'$'} - ${'withDollar$Sign'} | ${'dollar$ign'} | ${true} | ${1} | ${'$'} - ${'unsupported'} | ${'unsupported|char'} | ${true} | ${1} | ${'|'} - ${'unsupportedMasked'} | ${'unsupported|char'} | ${false} | ${0} | ${null} - `('Adding a new variable', ({ value, secret, masked, eventSent, trackingErrorProperty }) => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const invalidKeyVariable = { - ...variable, - key: 'key', - value, - secret_value: secret, - masked, - }; - createComponent(mount); - store.state.variable = invalidKeyVariable; - trackingSpy = mockTracking(undefined, wrapper.element, jest.spyOn); - }); - - it(`${ - eventSent > 0 ? 'sends the correct' : 'does not send the' - } variable validation tracking event`, () => { - expect(trackingSpy).toHaveBeenCalledTimes(eventSent); - - if (eventSent > 0) { - expect(trackingSpy).toHaveBeenCalledWith(undefined, EVENT_ACTION, { - label: EVENT_LABEL, - property: trackingErrorProperty, - }); - } - }); - }); - - describe('when both states are valid', () => { - beforeEach(() => { - const [variable] = mockData.mockVariables; - const validMaskandKeyVariable = { - ...variable, - key: AWS_ACCESS_KEY_ID, - value: '12345678', - secret_value: '87654321', - masked: true, - }; - createComponent(mount); - store.state.variable = validMaskandKeyVariable; - }); - - it('does not disable the submit button', () => { - expect(findAddorUpdateButton().attributes('disabled')).toBeUndefined(); - }); - }); - }); -}); diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js deleted file mode 100644 index 7def4dd4f29..00000000000 --- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_settings_spec.js +++ /dev/null @@ -1,38 +0,0 @@ -import { shallowMount } from '@vue/test-utils'; -import Vue from 'vue'; -import Vuex from 'vuex'; -import LegacyCiVariableSettings from '~/ci_variable_list/components/legacy_ci_variable_settings.vue'; -import createStore from '~/ci_variable_list/store'; - -Vue.use(Vuex); - -describe('Ci variable table', () => { - let wrapper; - let store; - let isProject; - - const createComponent = (projectState) => { - store = createStore(); - store.state.isProject = projectState; - jest.spyOn(store, 'dispatch').mockImplementation(); - wrapper = shallowMount(LegacyCiVariableSettings, { - store, - }); - }; - - afterEach(() => { - wrapper.destroy(); - }); - - it('dispatches fetchEnvironments when mounted', () => { - isProject = true; - createComponent(isProject); - expect(store.dispatch).toHaveBeenCalledWith('fetchEnvironments'); - }); - - it('does not dispatch fetchenvironments when in group context', () => { - isProject = false; - createComponent(isProject); - expect(store.dispatch).not.toHaveBeenCalled(); - }); -}); diff --git a/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js deleted file mode 100644 index 310afc8003a..00000000000 --- a/spec/frontend/ci_variable_list/components/legacy_ci_variable_table_spec.js +++ /dev/null @@ -1,86 +0,0 @@ -import Vue from 'vue'; -import Vuex from 'vuex'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import LegacyCiVariableTable from '~/ci_variable_list/components/legacy_ci_variable_table.vue'; -import createStore from '~/ci_variable_list/store'; -import mockData from '../services/mock_data'; - -Vue.use(Vuex); - -describe('Ci variable table', () => { - let wrapper; - let store; - - const createComponent = () => { - store = createStore(); - jest.spyOn(store, 'dispatch').mockImplementation(); - wrapper = mountExtended(LegacyCiVariableTable, { - attachTo: document.body, - store, - }); - }; - - const findRevealButton = () => wrapper.findByText('Reveal values'); - const findEditButton = () => wrapper.findByLabelText('Edit'); - const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.'); - - beforeEach(() => { - createComponent(); - }); - - afterEach(() => { - wrapper.destroy(); - }); - - it('dispatches fetchVariables when mounted', () => { - expect(store.dispatch).toHaveBeenCalledWith('fetchVariables'); - }); - - describe('When table is empty', () => { - beforeEach(() => { - store.state.variables = []; - }); - - it('displays empty message', () => { - expect(findEmptyVariablesPlaceholder().exists()).toBe(true); - }); - - it('hides the reveal button', () => { - expect(findRevealButton().exists()).toBe(false); - }); - }); - - describe('When table has variables', () => { - beforeEach(() => { - store.state.variables = mockData.mockVariables; - }); - - it('does not display the empty message', () => { - expect(findEmptyVariablesPlaceholder().exists()).toBe(false); - }); - - it('displays the reveal button', () => { - expect(findRevealButton().exists()).toBe(true); - }); - - it('displays the correct amount of variables', async () => { - expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(1); - }); - }); - - describe('Table click actions', () => { - beforeEach(() => { - store.state.variables = mockData.mockVariables; - }); - - it('reveals secret values when button is clicked', () => { - findRevealButton().trigger('click'); - expect(store.dispatch).toHaveBeenCalledWith('toggleValues', false); - }); - - it('dispatches editVariable with correct variable to edit', () => { - findEditButton().trigger('click'); - expect(store.dispatch).toHaveBeenCalledWith('editVariable', mockData.mockVariables[0]); - }); - }); -}); diff --git a/spec/frontend/ci_variable_list/mocks.js b/spec/frontend/ci_variable_list/mocks.js index 6f3e73f8b83..03b77f80430 100644 --- a/spec/frontend/ci_variable_list/mocks.js +++ b/spec/frontend/ci_variable_list/mocks.js @@ -1,10 +1,28 @@ import { + ADD_MUTATION_ACTION, + DELETE_MUTATION_ACTION, + UPDATE_MUTATION_ACTION, variableTypes, groupString, instanceString, projectString, } from '~/ci_variable_list/constants'; +import addAdminVariable from '~/ci_variable_list/graphql/mutations/admin_add_variable.mutation.graphql'; +import deleteAdminVariable from '~/ci_variable_list/graphql/mutations/admin_delete_variable.mutation.graphql'; +import updateAdminVariable from '~/ci_variable_list/graphql/mutations/admin_update_variable.mutation.graphql'; +import addGroupVariable from '~/ci_variable_list/graphql/mutations/group_add_variable.mutation.graphql'; +import deleteGroupVariable from '~/ci_variable_list/graphql/mutations/group_delete_variable.mutation.graphql'; +import updateGroupVariable from '~/ci_variable_list/graphql/mutations/group_update_variable.mutation.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 getAdminVariables from '~/ci_variable_list/graphql/queries/variables.query.graphql'; +import getGroupVariables from '~/ci_variable_list/graphql/queries/group_variables.query.graphql'; +import getProjectEnvironments from '~/ci_variable_list/graphql/queries/project_environments.query.graphql'; +import getProjectVariables from '~/ci_variable_list/graphql/queries/project_variables.query.graphql'; + export const devName = 'dev'; export const prodName = 'prod'; @@ -118,3 +136,62 @@ export const newVariable = { value: 'devops', variableType: variableTypes.variableType, }; + +export const createProjectProps = () => { + return { + componentName: 'ProjectVariable', + fullPath: '/namespace/project/', + id: 'gid://gitlab/Project/20', + mutationData: { + [ADD_MUTATION_ACTION]: addProjectVariable, + [UPDATE_MUTATION_ACTION]: updateProjectVariable, + [DELETE_MUTATION_ACTION]: deleteProjectVariable, + }, + queryData: { + ciVariables: { + lookup: (data) => data?.project?.ciVariables, + query: getProjectVariables, + }, + environments: { + lookup: (data) => data?.project?.environments, + query: getProjectEnvironments, + }, + }, + }; +}; + +export const createGroupProps = () => { + return { + componentName: 'GroupVariable', + fullPath: '/my-group', + id: 'gid://gitlab/Group/20', + mutationData: { + [ADD_MUTATION_ACTION]: addGroupVariable, + [UPDATE_MUTATION_ACTION]: updateGroupVariable, + [DELETE_MUTATION_ACTION]: deleteGroupVariable, + }, + queryData: { + ciVariables: { + lookup: (data) => data?.group?.ciVariables, + query: getGroupVariables, + }, + }, + }; +}; + +export const createInstanceProps = () => { + return { + componentName: 'InstanceVariable', + mutationData: { + [ADD_MUTATION_ACTION]: addAdminVariable, + [UPDATE_MUTATION_ACTION]: updateAdminVariable, + [DELETE_MUTATION_ACTION]: deleteAdminVariable, + }, + queryData: { + ciVariables: { + lookup: (data) => data?.ciVariables, + query: getAdminVariables, + }, + }, + }; +}; diff --git a/spec/frontend/ci_variable_list/store/actions_spec.js b/spec/frontend/ci_variable_list/store/actions_spec.js deleted file mode 100644 index e8c81a53a55..00000000000 --- a/spec/frontend/ci_variable_list/store/actions_spec.js +++ /dev/null @@ -1,319 +0,0 @@ -import MockAdapter from 'axios-mock-adapter'; -import testAction from 'helpers/vuex_action_helper'; -import Api from '~/api'; -import * as actions from '~/ci_variable_list/store/actions'; -import * as types from '~/ci_variable_list/store/mutation_types'; -import getInitialState from '~/ci_variable_list/store/state'; -import { prepareDataForDisplay, prepareEnvironments } from '~/ci_variable_list/store/utils'; -import { createAlert } from '~/flash'; -import axios from '~/lib/utils/axios_utils'; -import mockData from '../services/mock_data'; - -jest.mock('~/api.js'); -jest.mock('~/flash.js'); - -describe('CI variable list store actions', () => { - let mock; - let state; - const mockVariable = { - environment_scope: '*', - id: 63, - key: 'test_var', - masked: false, - protected: false, - value: 'test_val', - variable_type: 'env_var', - _destory: true, - }; - const payloadError = new Error('Request failed with status code 500'); - - beforeEach(() => { - mock = new MockAdapter(axios); - state = getInitialState(); - state.endpoint = '/variables'; - }); - - afterEach(() => { - mock.restore(); - }); - - describe('toggleValues', () => { - const valuesHidden = false; - it('commits TOGGLE_VALUES mutation', () => { - testAction(actions.toggleValues, valuesHidden, {}, [ - { - type: types.TOGGLE_VALUES, - payload: valuesHidden, - }, - ]); - }); - }); - - describe('clearModal', () => { - it('commits CLEAR_MODAL mutation', () => { - testAction(actions.clearModal, {}, {}, [ - { - type: types.CLEAR_MODAL, - }, - ]); - }); - }); - - describe('resetEditing', () => { - it('commits RESET_EDITING mutation', () => { - testAction( - actions.resetEditing, - {}, - {}, - [ - { - type: types.RESET_EDITING, - }, - ], - [{ type: 'fetchVariables' }], - ); - }); - }); - - describe('setVariableProtected', () => { - it('commits SET_VARIABLE_PROTECTED mutation', () => { - testAction(actions.setVariableProtected, {}, {}, [ - { - type: types.SET_VARIABLE_PROTECTED, - }, - ]); - }); - }); - - describe('deleteVariable', () => { - it('dispatch correct actions on successful deleted variable', () => { - mock.onPatch(state.endpoint).reply(200); - - return testAction( - actions.deleteVariable, - {}, - state, - [], - [ - { type: 'requestDeleteVariable' }, - { type: 'receiveDeleteVariableSuccess' }, - { type: 'fetchVariables' }, - ], - ); - }); - - it('should show flash error and set error in state on delete failure', async () => { - mock.onPatch(state.endpoint).reply(500, ''); - - await testAction( - actions.deleteVariable, - {}, - state, - [], - [ - { type: 'requestDeleteVariable' }, - { - type: 'receiveDeleteVariableError', - payload: payloadError, - }, - ], - ); - expect(createAlert).toHaveBeenCalled(); - }); - }); - - describe('updateVariable', () => { - it('dispatch correct actions on successful updated variable', () => { - mock.onPatch(state.endpoint).reply(200); - - return testAction( - actions.updateVariable, - {}, - state, - [], - [ - { type: 'requestUpdateVariable' }, - { type: 'receiveUpdateVariableSuccess' }, - { type: 'fetchVariables' }, - ], - ); - }); - - it('should show flash error and set error in state on update failure', async () => { - mock.onPatch(state.endpoint).reply(500, ''); - - await testAction( - actions.updateVariable, - mockVariable, - state, - [], - [ - { type: 'requestUpdateVariable' }, - { - type: 'receiveUpdateVariableError', - payload: payloadError, - }, - ], - ); - expect(createAlert).toHaveBeenCalled(); - }); - }); - - describe('addVariable', () => { - it('dispatch correct actions on successful added variable', () => { - mock.onPatch(state.endpoint).reply(200); - - return testAction( - actions.addVariable, - {}, - state, - [], - [ - { type: 'requestAddVariable' }, - { type: 'receiveAddVariableSuccess' }, - { type: 'fetchVariables' }, - ], - ); - }); - - it('should show flash error and set error in state on add failure', async () => { - mock.onPatch(state.endpoint).reply(500, ''); - - await testAction( - actions.addVariable, - {}, - state, - [], - [ - { type: 'requestAddVariable' }, - { - type: 'receiveAddVariableError', - payload: payloadError, - }, - ], - ); - expect(createAlert).toHaveBeenCalled(); - }); - }); - - describe('fetchVariables', () => { - it('dispatch correct actions on fetchVariables', () => { - mock.onGet(state.endpoint).reply(200, { variables: mockData.mockVariables }); - - return testAction( - actions.fetchVariables, - {}, - state, - [], - [ - { type: 'requestVariables' }, - { - type: 'receiveVariablesSuccess', - payload: prepareDataForDisplay(mockData.mockVariables), - }, - ], - ); - }); - - it('should show flash error and set error in state on fetch variables failure', async () => { - mock.onGet(state.endpoint).reply(500); - - await testAction(actions.fetchVariables, {}, state, [], [{ type: 'requestVariables' }]); - expect(createAlert).toHaveBeenCalledWith({ - message: 'There was an error fetching the variables.', - }); - }); - }); - - describe('fetchEnvironments', () => { - it('dispatch correct actions on fetchEnvironments', () => { - Api.environments = jest.fn().mockResolvedValue({ data: mockData.mockEnvironments }); - - return testAction( - actions.fetchEnvironments, - {}, - state, - [], - [ - { type: 'requestEnvironments' }, - { - type: 'receiveEnvironmentsSuccess', - payload: prepareEnvironments(mockData.mockEnvironments), - }, - ], - ); - }); - - it('should show flash error and set error in state on fetch environments failure', async () => { - Api.environments = jest.fn().mockRejectedValue(); - - await testAction(actions.fetchEnvironments, {}, state, [], [{ type: 'requestEnvironments' }]); - - expect(createAlert).toHaveBeenCalledWith({ - message: 'There was an error fetching the environments information.', - }); - }); - }); - - describe('Update variable values', () => { - it('updateVariableKey', () => { - testAction( - actions.updateVariableKey, - { key: mockVariable.key }, - {}, - [ - { - type: types.UPDATE_VARIABLE_KEY, - payload: mockVariable.key, - }, - ], - [], - ); - }); - - it('updateVariableValue', () => { - testAction( - actions.updateVariableValue, - { secret_value: mockVariable.value }, - {}, - [ - { - type: types.UPDATE_VARIABLE_VALUE, - payload: mockVariable.value, - }, - ], - [], - ); - }); - - it('updateVariableType', () => { - testAction( - actions.updateVariableType, - { variable_type: mockVariable.variable_type }, - {}, - [{ type: types.UPDATE_VARIABLE_TYPE, payload: mockVariable.variable_type }], - [], - ); - }); - - it('updateVariableProtected', () => { - testAction( - actions.updateVariableProtected, - { protected_variable: mockVariable.protected }, - {}, - [{ type: types.UPDATE_VARIABLE_PROTECTED, payload: mockVariable.protected }], - [], - ); - }); - - it('updateVariableMasked', () => { - testAction( - actions.updateVariableMasked, - { masked: mockVariable.masked }, - {}, - [{ type: types.UPDATE_VARIABLE_MASKED, payload: mockVariable.masked }], - [], - ); - }); - }); -}); diff --git a/spec/frontend/ci_variable_list/store/getters_spec.js b/spec/frontend/ci_variable_list/store/getters_spec.js deleted file mode 100644 index 92f22b18763..00000000000 --- a/spec/frontend/ci_variable_list/store/getters_spec.js +++ /dev/null @@ -1,21 +0,0 @@ -import * as getters from '~/ci_variable_list/store/getters'; -import mockData from '../services/mock_data'; - -describe('Ci variable getters', () => { - describe('joinedEnvironments', () => { - it('should join fetched environments with variable environment scopes', () => { - const state = { - environments: ['All (default)', 'staging', 'deployment', 'prod'], - variables: mockData.mockVariableScopes, - }; - - expect(getters.joinedEnvironments(state)).toEqual([ - 'All (default)', - 'deployment', - 'prod', - 'production', - 'staging', - ]); - }); - }); -}); diff --git a/spec/frontend/ci_variable_list/store/mutations_spec.js b/spec/frontend/ci_variable_list/store/mutations_spec.js deleted file mode 100644 index c7d07ead09b..00000000000 --- a/spec/frontend/ci_variable_list/store/mutations_spec.js +++ /dev/null @@ -1,136 +0,0 @@ -import * as types from '~/ci_variable_list/store/mutation_types'; -import mutations from '~/ci_variable_list/store/mutations'; -import state from '~/ci_variable_list/store/state'; - -describe('CI variable list mutations', () => { - let stateCopy; - - beforeEach(() => { - stateCopy = state(); - }); - - describe('TOGGLE_VALUES', () => { - it('should toggle state', () => { - const valuesHidden = false; - - mutations[types.TOGGLE_VALUES](stateCopy, valuesHidden); - - expect(stateCopy.valuesHidden).toEqual(valuesHidden); - }); - }); - - describe('VARIABLE_BEING_EDITED', () => { - it('should set the variable that is being edited', () => { - mutations[types.VARIABLE_BEING_EDITED](stateCopy); - - expect(stateCopy.variableBeingEdited).toBe(true); - }); - }); - - describe('RESET_EDITING', () => { - it('should reset variableBeingEdited to false', () => { - mutations[types.RESET_EDITING](stateCopy); - - expect(stateCopy.variableBeingEdited).toBe(false); - }); - }); - - describe('CLEAR_MODAL', () => { - it('should clear modal state', () => { - const modalState = { - variable_type: 'Variable', - key: '', - secret_value: '', - protected_variable: false, - masked: false, - environment_scope: 'All (default)', - }; - - mutations[types.CLEAR_MODAL](stateCopy); - - expect(stateCopy.variable).toEqual(modalState); - }); - }); - - describe('RECEIVE_ENVIRONMENTS_SUCCESS', () => { - it('should set environments', () => { - const environments = ['env1', 'env2']; - - mutations[types.RECEIVE_ENVIRONMENTS_SUCCESS](stateCopy, environments); - - expect(stateCopy.environments).toEqual(['All (default)', 'env1', 'env2']); - }); - }); - - describe('SET_ENVIRONMENT_SCOPE', () => { - const environment = 'production'; - - it('should set environment scope on variable', () => { - mutations[types.SET_ENVIRONMENT_SCOPE](stateCopy, environment); - - expect(stateCopy.variable.environment_scope).toBe('production'); - }); - }); - - describe('ADD_WILD_CARD_SCOPE', () => { - it('should add wild card scope to environments array and sort', () => { - stateCopy.environments = ['dev', 'staging']; - mutations[types.ADD_WILD_CARD_SCOPE](stateCopy, 'production'); - - expect(stateCopy.environments).toEqual(['dev', 'production', 'staging']); - }); - }); - - describe('SET_VARIABLE_PROTECTED', () => { - it('should set protected value to true', () => { - mutations[types.SET_VARIABLE_PROTECTED](stateCopy); - - expect(stateCopy.variable.protected_variable).toBe(true); - }); - }); - - describe('UPDATE_VARIABLE_KEY', () => { - it('should update variable key value', () => { - const key = 'new_var'; - mutations[types.UPDATE_VARIABLE_KEY](stateCopy, key); - - expect(stateCopy.variable.key).toBe(key); - }); - }); - - describe('UPDATE_VARIABLE_VALUE', () => { - it('should update variable value', () => { - const value = 'variable_value'; - mutations[types.UPDATE_VARIABLE_VALUE](stateCopy, value); - - expect(stateCopy.variable.secret_value).toBe(value); - }); - }); - - describe('UPDATE_VARIABLE_TYPE', () => { - it('should update variable type value', () => { - const type = 'File'; - mutations[types.UPDATE_VARIABLE_TYPE](stateCopy, type); - - expect(stateCopy.variable.variable_type).toBe(type); - }); - }); - - describe('UPDATE_VARIABLE_PROTECTED', () => { - it('should update variable protected value', () => { - const protectedValue = true; - mutations[types.UPDATE_VARIABLE_PROTECTED](stateCopy, protectedValue); - - expect(stateCopy.variable.protected_variable).toBe(protectedValue); - }); - }); - - describe('UPDATE_VARIABLE_MASKED', () => { - it('should update variable masked value', () => { - const masked = true; - mutations[types.UPDATE_VARIABLE_MASKED](stateCopy, masked); - - expect(stateCopy.variable.masked).toBe(masked); - }); - }); -}); diff --git a/spec/frontend/ci_variable_list/store/utils_spec.js b/spec/frontend/ci_variable_list/store/utils_spec.js deleted file mode 100644 index 5b10370324a..00000000000 --- a/spec/frontend/ci_variable_list/store/utils_spec.js +++ /dev/null @@ -1,49 +0,0 @@ -import { - prepareDataForDisplay, - prepareEnvironments, - prepareDataForApi, -} from '~/ci_variable_list/store/utils'; -import mockData from '../services/mock_data'; - -describe('CI variables store utils', () => { - it('prepares ci variables for display', () => { - expect(prepareDataForDisplay(mockData.mockVariablesApi)).toStrictEqual( - mockData.mockVariablesDisplay, - ); - }); - - it('prepares single ci variable for api', () => { - expect(prepareDataForApi(mockData.mockVariablesDisplay[0])).toStrictEqual({ - environment_scope: '*', - id: 113, - key: 'test_var', - masked: 'false', - protected: 'false', - secret_value: 'test_val', - value: 'test_val', - variable_type: 'env_var', - }); - - expect(prepareDataForApi(mockData.mockVariablesDisplay[1])).toStrictEqual({ - environment_scope: '*', - id: 114, - key: 'test_var_2', - masked: 'false', - protected: 'false', - secret_value: 'test_val_2', - value: 'test_val_2', - variable_type: 'file', - }); - }); - - it('prepares single ci variable for delete', () => { - expect(prepareDataForApi(mockData.mockVariablesDisplay[0], true)).toHaveProperty( - '_destroy', - true, - ); - }); - - it('prepares environments for display', () => { - expect(prepareEnvironments(mockData.mockEnvironments)).toStrictEqual(['staging', 'production']); - }); -}); |