diff options
Diffstat (limited to 'spec/frontend/runner/components/cells/runner_actions_cell_spec.js')
-rw-r--r-- | spec/frontend/runner/components/cells/runner_actions_cell_spec.js | 234 |
1 files changed, 180 insertions, 54 deletions
diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js index 12651a82a0c..95f7c38cafc 100644 --- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js +++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js @@ -1,18 +1,30 @@ -import { shallowMount } from '@vue/test-utils'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import createFlash from '~/flash'; import RunnerActionCell from '~/runner/components/cells/runner_actions_cell.vue'; -import deleteRunnerMutation from '~/runner/graphql/delete_runner.mutation.graphql'; import getRunnersQuery from '~/runner/graphql/get_runners.query.graphql'; +import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql'; import runnerUpdateMutation from '~/runner/graphql/runner_update.mutation.graphql'; +import { captureException } from '~/runner/sentry_utils'; +import { runnerData } from '../../mock_data'; -const mockId = '1'; +const mockRunner = runnerData.data.runner; const getRunnersQueryName = getRunnersQuery.definitions[0].name.value; +const localVue = createLocalVue(); +localVue.use(VueApollo); + +jest.mock('~/flash'); +jest.mock('~/runner/sentry_utils'); + describe('RunnerTypeCell', () => { let wrapper; - let mutate; + const runnerDeleteMutationHandler = jest.fn(); + const runnerUpdateMutationHandler = jest.fn(); const findEditBtn = () => wrapper.findByTestId('edit-runner'); const findToggleActiveBtn = () => wrapper.findByTestId('toggle-active-runner'); @@ -23,26 +35,43 @@ describe('RunnerTypeCell', () => { shallowMount(RunnerActionCell, { propsData: { runner: { - id: `gid://gitlab/Ci::Runner/${mockId}`, + id: mockRunner.id, active, }, }, - mocks: { - $apollo: { - mutate, - }, - }, + localVue, + apolloProvider: createMockApollo([ + [runnerDeleteMutation, runnerDeleteMutationHandler], + [runnerUpdateMutation, runnerUpdateMutationHandler], + ]), ...options, }), ); }; beforeEach(() => { - mutate = jest.fn(); + runnerDeleteMutationHandler.mockResolvedValue({ + data: { + runnerDelete: { + errors: [], + }, + }, + }); + + runnerUpdateMutationHandler.mockResolvedValue({ + data: { + runnerUpdate: { + runner: runnerData.data.runner, + errors: [], + }, + }, + }); }); afterEach(() => { - mutate.mockReset(); + runnerDeleteMutationHandler.mockReset(); + runnerUpdateMutationHandler.mockReset(); + wrapper.destroy(); }); @@ -58,17 +87,6 @@ describe('RunnerTypeCell', () => { ${'paused'} | ${'Resume'} | ${'play'} | ${false} | ${true} `('When the runner is $state', ({ label, icon, isActive, newActiveValue }) => { beforeEach(() => { - mutate.mockResolvedValue({ - data: { - runnerUpdate: { - runner: { - id: `gid://gitlab/Ci::Runner/1`, - __typename: 'CiRunner', - }, - }, - }, - }); - createComponent({ active: isActive }); }); @@ -93,46 +111,93 @@ describe('RunnerTypeCell', () => { }); describe(`When clicking on the ${icon} button`, () => { - beforeEach(async () => { + it(`The apollo mutation to set active to ${newActiveValue} is called`, async () => { + expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(0); + await findToggleActiveBtn().vm.$emit('click'); - await waitForPromises(); - }); - it(`The apollo mutation to set active to ${newActiveValue} is called`, () => { - expect(mutate).toHaveBeenCalledTimes(1); - expect(mutate).toHaveBeenCalledWith({ - mutation: runnerUpdateMutation, - variables: { - input: { - id: `gid://gitlab/Ci::Runner/${mockId}`, - active: newActiveValue, - }, + expect(runnerUpdateMutationHandler).toHaveBeenCalledTimes(1); + expect(runnerUpdateMutationHandler).toHaveBeenCalledWith({ + input: { + id: mockRunner.id, + active: newActiveValue, }, }); }); - it('The button does not have a loading state', () => { + it('The button does not have a loading state after the mutation occurs', async () => { + await findToggleActiveBtn().vm.$emit('click'); + + expect(findToggleActiveBtn().props('loading')).toBe(true); + + await waitForPromises(); + expect(findToggleActiveBtn().props('loading')).toBe(false); }); }); - }); - describe('When the user clicks a runner', () => { - beforeEach(() => { - createComponent(); + describe('When update fails', () => { + describe('On a network error', () => { + const mockErrorMsg = 'Update error!'; + + beforeEach(async () => { + runnerUpdateMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg)); + + await findToggleActiveBtn().vm.$emit('click'); + }); + + it('error is reported to sentry', () => { + expect(captureException).toHaveBeenCalledWith({ + error: new Error(`Network error: ${mockErrorMsg}`), + component: 'RunnerActionsCell', + }); + }); - mutate.mockResolvedValue({ - data: { - runnerDelete: { - runner: { - id: `gid://gitlab/Ci::Runner/1`, - __typename: 'CiRunner', + it('error is shown to the user', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + }); + }); + + describe('On a validation error', () => { + const mockErrorMsg = 'Runner not found!'; + const mockErrorMsg2 = 'User not allowed!'; + + beforeEach(async () => { + runnerUpdateMutationHandler.mockResolvedValue({ + data: { + runnerUpdate: { + runner: runnerData.data.runner, + errors: [mockErrorMsg, mockErrorMsg2], + }, }, - }, - }, + }); + + await findToggleActiveBtn().vm.$emit('click'); + }); + + it('error is reported to sentry', () => { + expect(captureException).toHaveBeenCalledWith({ + error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`), + component: 'RunnerActionsCell', + }); + }); + + it('error is shown to the user', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + }); }); + }); + }); + describe('When the user clicks a runner', () => { + beforeEach(() => { jest.spyOn(window, 'confirm'); + + createComponent(); + }); + + afterEach(() => { + window.confirm.mockRestore(); }); describe('When the user confirms deletion', () => { @@ -141,18 +206,28 @@ describe('RunnerTypeCell', () => { await findDeleteBtn().vm.$emit('click'); }); - it('The user sees a confirmation alert', async () => { + it('The user sees a confirmation alert', () => { expect(window.confirm).toHaveBeenCalledTimes(1); expect(window.confirm).toHaveBeenCalledWith(expect.any(String)); }); it('The delete mutation is called correctly', () => { - expect(mutate).toHaveBeenCalledTimes(1); - expect(mutate).toHaveBeenCalledWith({ - mutation: deleteRunnerMutation, + expect(runnerDeleteMutationHandler).toHaveBeenCalledTimes(1); + expect(runnerDeleteMutationHandler).toHaveBeenCalledWith({ + input: { id: mockRunner.id }, + }); + }); + + it('When delete mutation is called, current runners are refetched', async () => { + jest.spyOn(wrapper.vm.$apollo, 'mutate'); + + await findDeleteBtn().vm.$emit('click'); + + expect(wrapper.vm.$apollo.mutate).toHaveBeenCalledWith({ + mutation: runnerDeleteMutation, variables: { input: { - id: `gid://gitlab/Ci::Runner/${mockId}`, + id: mockRunner.id, }, }, awaitRefetchQueries: true, @@ -176,6 +251,57 @@ describe('RunnerTypeCell', () => { expect(findDeleteBtn().attributes('title')).toBe(''); }); + + describe('When delete fails', () => { + describe('On a network error', () => { + const mockErrorMsg = 'Delete error!'; + + beforeEach(async () => { + runnerDeleteMutationHandler.mockRejectedValueOnce(new Error(mockErrorMsg)); + + await findDeleteBtn().vm.$emit('click'); + }); + + it('error is reported to sentry', () => { + expect(captureException).toHaveBeenCalledWith({ + error: new Error(`Network error: ${mockErrorMsg}`), + component: 'RunnerActionsCell', + }); + }); + + it('error is shown to the user', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + }); + }); + + describe('On a validation error', () => { + const mockErrorMsg = 'Runner not found!'; + const mockErrorMsg2 = 'User not allowed!'; + + beforeEach(async () => { + runnerDeleteMutationHandler.mockResolvedValue({ + data: { + runnerDelete: { + errors: [mockErrorMsg, mockErrorMsg2], + }, + }, + }); + + await findDeleteBtn().vm.$emit('click'); + }); + + it('error is reported to sentry', () => { + expect(captureException).toHaveBeenCalledWith({ + error: new Error(`${mockErrorMsg} ${mockErrorMsg2}`), + component: 'RunnerActionsCell', + }); + }); + + it('error is shown to the user', () => { + expect(createFlash).toHaveBeenCalledTimes(1); + }); + }); + }); }); describe('When the user does not confirm deletion', () => { @@ -189,7 +315,7 @@ describe('RunnerTypeCell', () => { }); it('The delete mutation is not called', () => { - expect(mutate).toHaveBeenCalledTimes(0); + expect(runnerDeleteMutationHandler).toHaveBeenCalledTimes(0); }); it('The delete button does not have a loading state', () => { |