diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-01-20 12:16:11 +0300 |
commit | edaa33dee2ff2f7ea3fac488d41558eb5f86d68c (patch) | |
tree | 11f143effbfeba52329fb7afbd05e6e2a3790241 /spec/frontend/clusters_list | |
parent | d8a5691316400a0f7ec4f83832698f1988eb27c1 (diff) |
Add latest changes from gitlab-org/gitlab@14-7-stable-eev14.7.0-rc42
Diffstat (limited to 'spec/frontend/clusters_list')
4 files changed, 249 insertions, 2 deletions
diff --git a/spec/frontend/clusters_list/components/agent_options_spec.js b/spec/frontend/clusters_list/components/agent_options_spec.js new file mode 100644 index 00000000000..05bab247816 --- /dev/null +++ b/spec/frontend/clusters_list/components/agent_options_spec.js @@ -0,0 +1,211 @@ +import { GlDropdown, GlDropdownItem, GlModal, GlFormInput } from '@gitlab/ui'; +import Vue 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 AgentOptions from '~/clusters_list/components/agent_options.vue'; +import { MAX_LIST_COUNT } from '~/clusters_list/constants'; +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('AgentOptions', () => { + let wrapper; + let toast; + let apolloProvider; + let deleteResponse; + + const findModal = () => wrapper.findComponent(GlModal); + const findDropdown = () => wrapper.findComponent(GlDropdown); + const findDeleteBtn = () => wrapper.findComponent(GlDropdownItem); + const findInput = () => wrapper.findComponent(GlFormInput); + const findPrimaryAction = () => findModal().props('actionPrimary'); + const findPrimaryActionAttributes = (attr) => findPrimaryAction().attributes[0][attr]; + + 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 = ({ mutationResponse = mockDeleteResponse } = {}) => { + apolloProvider = createMockApolloProvider({ mutationResponse }); + const provide = { + projectPath, + }; + const propsData = { + defaultBranchName, + maxAgents, + agent, + }; + + toast = jest.fn(); + + wrapper = shallowMountExtended(AgentOptions, { + apolloProvider, + provide, + propsData, + mocks: { $toast: { show: toast } }, + stubs: { GlModal }, + }); + wrapper.vm.$refs.modal.hide = jest.fn(); + + writeQuery(); + return wrapper.vm.$nextTick(); + }; + + const submitAgentToDelete = async () => { + findDeleteBtn().vm.$emit('click'); + findInput().vm.$emit('input', agent.name); + await findModal().vm.$emit('primary'); + }; + + beforeEach(() => { + return createWrapper({}); + }); + + afterEach(() => { + wrapper.destroy(); + apolloProvider = null; + deleteResponse = null; + toast = null; + }); + + describe('delete agent action', () => { + it('displays a delete button', () => { + expect(findDeleteBtn().text()).toBe('Delete agent'); + }); + + describe('when clicking the delete button', () => { + beforeEach(() => { + findDeleteBtn().vm.$emit('click'); + }); + + it('displays a delete confirmation modal', () => { + expect(findModal().isVisible()).toBe(true); + }); + }); + + 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 }); + + 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 }); + + submitAgentToDelete(); + }); + + it('reenables the options dropdown', async () => { + expect(findPrimaryActionAttributes('loading')).toBe(true); + expect(findDropdown().attributes('disabled')).toBe('true'); + + await findModal().vm.$emit('hide'); + + expect(findPrimaryActionAttributes('loading')).toBe(false); + expect(findDropdown().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(); + }); + }); +}); diff --git a/spec/frontend/clusters_list/components/agent_table_spec.js b/spec/frontend/clusters_list/components/agent_table_spec.js index a6d76b069cf..887c17bb4ad 100644 --- a/spec/frontend/clusters_list/components/agent_table_spec.js +++ b/spec/frontend/clusters_list/components/agent_table_spec.js @@ -1,16 +1,22 @@ import { GlLink, GlIcon } from '@gitlab/ui'; import AgentTable from '~/clusters_list/components/agent_table.vue'; +import AgentOptions from '~/clusters_list/components/agent_options.vue'; import { ACTIVE_CONNECTION_TIME } from '~/clusters_list/constants'; import { mountExtended } from 'helpers/vue_test_utils_helper'; +import { stubComponent } from 'helpers/stub_component'; import timeagoMixin from '~/vue_shared/mixins/timeago'; const connectedTimeNow = new Date(); const connectedTimeInactive = new Date(connectedTimeNow.getTime() - ACTIVE_CONNECTION_TIME); +const provideData = { + projectPath: 'path/to/project', +}; const propsData = { agents: [ { name: 'agent-1', + id: 'agent-1-id', configFolder: { webPath: '/agent/full/path', }, @@ -21,6 +27,7 @@ const propsData = { }, { name: 'agent-2', + id: 'agent-2-id', webPath: '/agent-2', status: 'active', lastContact: connectedTimeNow.getTime(), @@ -34,6 +41,7 @@ const propsData = { }, { name: 'agent-3', + id: 'agent-3-id', webPath: '/agent-3', status: 'inactive', lastContact: connectedTimeInactive.getTime(), @@ -48,6 +56,10 @@ const propsData = { ], }; +const AgentOptionsStub = stubComponent(AgentOptions, { + template: `<div></div>`, +}); + describe('AgentTable', () => { let wrapper; @@ -57,15 +69,21 @@ describe('AgentTable', () => { const findLastContactText = (at) => wrapper.findAllByTestId('cluster-agent-last-contact').at(at); const findConfiguration = (at) => wrapper.findAllByTestId('cluster-agent-configuration-link').at(at); + const findAgentOptions = () => wrapper.findAllComponents(AgentOptions); beforeEach(() => { - wrapper = mountExtended(AgentTable, { propsData }); + wrapper = mountExtended(AgentTable, { + propsData, + provide: provideData, + stubs: { + AgentOptions: AgentOptionsStub, + }, + }); }); afterEach(() => { if (wrapper) { wrapper.destroy(); - wrapper = null; } }); @@ -108,5 +126,9 @@ describe('AgentTable', () => { expect(findLink.exists()).toBe(hasLink); expect(findConfiguration(lineNumber).text()).toBe(agentPath); }); + + it('displays actions menu for each agent', () => { + expect(findAgentOptions()).toHaveLength(3); + }); }); }); diff --git a/spec/frontend/clusters_list/components/clusters_spec.js b/spec/frontend/clusters_list/components/clusters_spec.js index a34202c789d..9af25a534d8 100644 --- a/spec/frontend/clusters_list/components/clusters_spec.js +++ b/spec/frontend/clusters_list/components/clusters_spec.js @@ -272,6 +272,8 @@ describe('Clusters', () => { describe('when updating currentPage', () => { beforeEach(() => { mockPollingApi(200, apiData, paginationHeader(totalSecondPage, perPage, 2)); + // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details + // eslint-disable-next-line no-restricted-syntax wrapper.setData({ currentPage: 2 }); return axios.waitForAll(); }); diff --git a/spec/frontend/clusters_list/mocks/apollo.js b/spec/frontend/clusters_list/mocks/apollo.js index 804f9834506..c4a31ed4394 100644 --- a/spec/frontend/clusters_list/mocks/apollo.js +++ b/spec/frontend/clusters_list/mocks/apollo.js @@ -75,3 +75,15 @@ export const getAgentResponse = { }, }, }; + +export const mockDeleteResponse = { + data: { clusterAgentDelete: { errors: [] } }, +}; + +export const mockErrorDeleteResponse = { + data: { + clusterAgentDelete: { + errors: ['could not delete agent'], + }, + }, +}; |