diff options
Diffstat (limited to 'spec/frontend/clusters_list/components/delete_agent_button_spec.js')
-rw-r--r-- | spec/frontend/clusters_list/components/delete_agent_button_spec.js | 250 |
1 files changed, 250 insertions, 0 deletions
diff --git a/spec/frontend/clusters_list/components/delete_agent_button_spec.js b/spec/frontend/clusters_list/components/delete_agent_button_spec.js new file mode 100644 index 00000000000..82850b9dea4 --- /dev/null +++ b/spec/frontend/clusters_list/components/delete_agent_button_spec.js @@ -0,0 +1,250 @@ +import { GlButton, GlModal, GlFormInput } from '@gitlab/ui'; +import Vue, { nextTick } from 'vue'; +import VueApollo from 'vue-apollo'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { ENTER_KEY } from '~/lib/utils/keys'; +import getAgentsQuery from '~/clusters_list/graphql/queries/get_agents.query.graphql'; +import deleteAgentMutation from '~/clusters_list/graphql/mutations/delete_agent.mutation.graphql'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; +import DeleteAgentButton from '~/clusters_list/components/delete_agent_button.vue'; +import { MAX_LIST_COUNT, DELETE_AGENT_BUTTON } from '~/clusters_list/constants'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { getAgentResponse, mockDeleteResponse, mockErrorDeleteResponse } from '../mocks/apollo'; + +Vue.use(VueApollo); + +const projectPath = 'path/to/project'; +const defaultBranchName = 'default'; +const maxAgents = MAX_LIST_COUNT; +const agent = { + id: 'agent-id', + name: 'agent-name', + webPath: 'agent-webPath', +}; + +describe('DeleteAgentButton', () => { + let wrapper; + let toast; + let apolloProvider; + let deleteResponse; + + const findModal = () => wrapper.findComponent(GlModal); + const findDeleteBtn = () => wrapper.findComponent(GlButton); + const findInput = () => wrapper.findComponent(GlFormInput); + const findPrimaryAction = () => findModal().props('actionPrimary'); + const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr]; + const findDeleteAgentButtonTooltip = () => wrapper.findByTestId('delete-agent-button-tooltip'); + const getTooltipText = (el) => { + const binding = getBinding(el, 'gl-tooltip'); + + return binding.value; + }; + + const createMockApolloProvider = ({ mutationResponse }) => { + deleteResponse = jest.fn().mockResolvedValue(mutationResponse); + + return createMockApollo([[deleteAgentMutation, deleteResponse]]); + }; + + const writeQuery = () => { + apolloProvider.clients.defaultClient.cache.writeQuery({ + query: getAgentsQuery, + variables: { + projectPath, + defaultBranchName, + first: maxAgents, + last: null, + }, + data: getAgentResponse.data, + }); + }; + + const createWrapper = async ({ + mutationResponse = mockDeleteResponse, + provideData = {}, + } = {}) => { + apolloProvider = createMockApolloProvider({ mutationResponse }); + const defaultProvide = { + projectPath, + canAdminCluster: true, + }; + const propsData = { + defaultBranchName, + maxAgents, + agent, + }; + + toast = jest.fn(); + + wrapper = shallowMountExtended(DeleteAgentButton, { + apolloProvider, + provide: { + ...defaultProvide, + ...provideData, + }, + directives: { + GlTooltip: createMockDirective(), + }, + propsData, + mocks: { $toast: { show: toast } }, + stubs: { GlModal }, + }); + wrapper.vm.$refs.modal.hide = jest.fn(); + + writeQuery(); + await nextTick(); + }; + + const submitAgentToDelete = async () => { + findDeleteBtn().vm.$emit('click'); + findInput().vm.$emit('input', agent.name); + await findModal().vm.$emit('primary'); + await waitForPromises(); + }; + + beforeEach(() => { + return createWrapper({}); + }); + + afterEach(() => { + wrapper.destroy(); + apolloProvider = null; + deleteResponse = null; + toast = null; + }); + + describe('delete agent action', () => { + it('displays a delete button', () => { + expect(findDeleteBtn().attributes('aria-label')).toBe(DELETE_AGENT_BUTTON.deleteButton); + }); + + it('shows a tooltip for the button', () => { + expect(getTooltipText(findDeleteAgentButtonTooltip().element)).toBe( + DELETE_AGENT_BUTTON.deleteButton, + ); + }); + + describe('when clicking the delete button', () => { + beforeEach(() => { + findDeleteBtn().vm.$emit('click'); + }); + + it('displays a delete confirmation modal', () => { + expect(findModal().isVisible()).toBe(true); + }); + }); + + describe('when user cannot delete clusters', () => { + beforeEach(() => { + createWrapper({ provideData: { canAdminCluster: false } }); + }); + + it('disables the button', () => { + expect(findDeleteBtn().attributes('disabled')).toBe('true'); + }); + + it('shows a disabled tooltip', () => { + expect(getTooltipText(findDeleteAgentButtonTooltip().element)).toBe( + DELETE_AGENT_BUTTON.disabledHint, + ); + }); + }); + + describe.each` + condition | agentName | isDisabled | mutationCalled + ${'the input with agent name is missing'} | ${''} | ${true} | ${false} + ${'the input with agent name is incorrect'} | ${'wrong-name'} | ${true} | ${false} + ${'the input with agent name is correct'} | ${agent.name} | ${false} | ${true} + `('when $condition', ({ agentName, isDisabled, mutationCalled }) => { + beforeEach(() => { + findDeleteBtn().vm.$emit('click'); + findInput().vm.$emit('input', agentName); + }); + + it(`${isDisabled ? 'disables' : 'enables'} the modal primary button`, () => { + expect(findPrimaryActionAttributes('disabled')).toBe(isDisabled); + }); + + describe('when user clicks the modal primary button', () => { + beforeEach(async () => { + await findModal().vm.$emit('primary'); + }); + + if (mutationCalled) { + it('calls the delete mutation', () => { + expect(deleteResponse).toHaveBeenCalledWith({ input: { id: agent.id } }); + }); + } else { + it("doesn't call the delete mutation", () => { + expect(deleteResponse).not.toHaveBeenCalled(); + }); + } + }); + + describe('when user presses the enter button', () => { + beforeEach(async () => { + await findInput().vm.$emit('keydown', new KeyboardEvent({ key: ENTER_KEY })); + }); + + if (mutationCalled) { + it('calls the delete mutation', () => { + expect(deleteResponse).toHaveBeenCalledWith({ input: { id: agent.id } }); + }); + } else { + it("doesn't call the delete mutation", () => { + expect(deleteResponse).not.toHaveBeenCalled(); + }); + } + }); + }); + + describe('when agent was deleted successfully', () => { + beforeEach(async () => { + await submitAgentToDelete(); + }); + + it('calls the toast action', () => { + expect(toast).toHaveBeenCalledWith(`${agent.name} successfully deleted`); + }); + }); + }); + + describe('when getting an error deleting agent', () => { + beforeEach(async () => { + await createWrapper({ mutationResponse: mockErrorDeleteResponse }); + await submitAgentToDelete(); + }); + + it('displays the error message', () => { + expect(toast).toHaveBeenCalledWith('could not delete agent'); + }); + }); + + describe('when the delete modal was closed', () => { + beforeEach(async () => { + const loadingResponse = new Promise(() => {}); + await createWrapper({ mutationResponse: loadingResponse }); + + await submitAgentToDelete(); + }); + + it('reenables the button', async () => { + expect(findPrimaryActionAttributes('loading')).toBe(true); + expect(findDeleteBtn().attributes('disabled')).toBe('true'); + + await findModal().vm.$emit('hide'); + + expect(findPrimaryActionAttributes('loading')).toBe(false); + expect(findDeleteBtn().attributes('disabled')).toBeUndefined(); + }); + + it('clears the agent name input', async () => { + expect(findInput().attributes('value')).toBe(agent.name); + + await findModal().vm.$emit('hide'); + + expect(findInput().attributes('value')).toBeUndefined(); + }); + }); +}); |