diff options
Diffstat (limited to 'spec/frontend/ci/runner/components/runner_bulk_delete_spec.js')
-rw-r--r-- | spec/frontend/ci/runner/components/runner_bulk_delete_spec.js | 295 |
1 files changed, 295 insertions, 0 deletions
diff --git a/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js new file mode 100644 index 00000000000..64f5a0e3b57 --- /dev/null +++ b/spec/frontend/ci/runner/components/runner_bulk_delete_spec.js @@ -0,0 +1,295 @@ +import Vue from 'vue'; +import { GlModal, GlSprintf } from '@gitlab/ui'; +import VueApollo from 'vue-apollo'; +import { createAlert } from '~/flash'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import { s__ } from '~/locale'; +import RunnerBulkDelete from '~/ci/runner/components/runner_bulk_delete.vue'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import BulkRunnerDeleteMutation from '~/ci/runner/graphql/list/bulk_runner_delete.mutation.graphql'; +import { createLocalState } from '~/ci/runner/graphql/list/local_state'; +import waitForPromises from 'helpers/wait_for_promises'; +import { allRunnersData } from '../mock_data'; + +Vue.use(VueApollo); + +jest.mock('~/flash'); + +describe('RunnerBulkDelete', () => { + let wrapper; + let apolloCache; + let mockState; + let mockCheckedRunnerIds; + + const findClearBtn = () => wrapper.findByText(s__('Runners|Clear selection')); + const findDeleteBtn = () => wrapper.findByText(s__('Runners|Delete selected')); + const findModal = () => wrapper.findComponent(GlModal); + + const mockRunners = allRunnersData.data.runners.nodes; + const mockId1 = allRunnersData.data.runners.nodes[0].id; + const mockId2 = allRunnersData.data.runners.nodes[1].id; + + const bulkRunnerDeleteHandler = jest.fn(); + + const createComponent = () => { + const { cacheConfig, localMutations } = mockState; + const apolloProvider = createMockApollo( + [[BulkRunnerDeleteMutation, bulkRunnerDeleteHandler]], + undefined, + cacheConfig, + ); + + wrapper = shallowMountExtended(RunnerBulkDelete, { + apolloProvider, + provide: { + localMutations, + }, + propsData: { + runners: mockRunners, + }, + directives: { + GlTooltip: createMockDirective(), + }, + stubs: { + GlSprintf, + GlModal, + }, + }); + + apolloCache = apolloProvider.defaultClient.cache; + jest.spyOn(apolloCache, 'evict'); + jest.spyOn(apolloCache, 'gc'); + }; + + beforeEach(() => { + mockState = createLocalState(); + + jest + .spyOn(mockState.cacheConfig.typePolicies.Query.fields, 'checkedRunnerIds') + .mockImplementation(() => mockCheckedRunnerIds); + }); + + afterEach(() => { + bulkRunnerDeleteHandler.mockReset(); + }); + + describe('When no runners are checked', () => { + beforeEach(async () => { + mockCheckedRunnerIds = []; + + createComponent(); + + await waitForPromises(); + }); + + it('shows no contents', () => { + expect(wrapper.html()).toBe(''); + }); + }); + + describe.each` + count | ids | text + ${1} | ${[mockId1]} | ${'1 runner'} + ${2} | ${[mockId1, mockId2]} | ${'2 runners'} + `('When $count runner(s) are checked', ({ ids, text }) => { + beforeEach(() => { + mockCheckedRunnerIds = ids; + + createComponent(); + + jest.spyOn(mockState.localMutations, 'clearChecked').mockImplementation(() => {}); + }); + + it(`shows "${text}"`, () => { + expect(wrapper.text()).toContain(text); + }); + + it('clears selection', () => { + expect(mockState.localMutations.clearChecked).toHaveBeenCalledTimes(0); + + findClearBtn().vm.$emit('click'); + + expect(mockState.localMutations.clearChecked).toHaveBeenCalledTimes(1); + }); + + it('shows confirmation modal', () => { + const modalId = getBinding(findDeleteBtn().element, 'gl-modal'); + + expect(findModal().props('modal-id')).toBe(modalId); + expect(findModal().text()).toContain(text); + }); + }); + + describe('when runners are deleted', () => { + let evt; + let mockHideModal; + + const confirmDeletion = () => { + evt = { + preventDefault: jest.fn(), + }; + findModal().vm.$emit('primary', evt); + }; + + beforeEach(() => { + mockCheckedRunnerIds = [mockId1, mockId2]; + + createComponent(); + + jest.spyOn(mockState.localMutations, 'clearChecked').mockImplementation(() => {}); + mockHideModal = jest.spyOn(findModal().vm, 'hide').mockImplementation(() => {}); + }); + + describe('when deletion is confirmed', () => { + beforeEach(() => { + confirmDeletion(); + }); + + it('has loading state', () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(true); + expect(findModal().props('actionCancel').attributes.loading).toBe(true); + }); + + it('modal is not prevented from closing', () => { + expect(evt.preventDefault).toHaveBeenCalledTimes(1); + }); + + it('mutation is called', () => { + expect(bulkRunnerDeleteHandler).toHaveBeenCalledWith({ + input: { ids: mockCheckedRunnerIds }, + }); + }); + }); + + describe('when deletion is successful', () => { + beforeEach(async () => { + bulkRunnerDeleteHandler.mockResolvedValue({ + data: { + bulkRunnerDelete: { deletedIds: mockCheckedRunnerIds, errors: [] }, + }, + }); + + confirmDeletion(); + await waitForPromises(); + }); + + it('removes loading state', () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findModal().props('actionCancel').attributes.loading).toBe(false); + }); + + it('user interface is updated', () => { + const { evict, gc } = apolloCache; + + expect(evict).toHaveBeenCalledTimes(mockCheckedRunnerIds.length); + expect(evict).toHaveBeenCalledWith({ + id: expect.stringContaining(mockCheckedRunnerIds[0]), + }); + expect(evict).toHaveBeenCalledWith({ + id: expect.stringContaining(mockCheckedRunnerIds[1]), + }); + + expect(gc).toHaveBeenCalledTimes(1); + }); + + it('emits deletion confirmation', () => { + expect(wrapper.emitted('deleted')).toEqual([ + [{ message: expect.stringContaining(`${mockCheckedRunnerIds.length}`) }], + ]); + }); + + it('modal is hidden', () => { + expect(mockHideModal).toHaveBeenCalledTimes(1); + }); + }); + + describe('when deletion fails partially', () => { + beforeEach(async () => { + bulkRunnerDeleteHandler.mockResolvedValue({ + data: { + bulkRunnerDelete: { + deletedIds: [mockId1], // only one runner could be deleted + errors: ['Can only delete up to 1 runners per call. Ignored 1 runner(s).'], + }, + }, + }); + + confirmDeletion(); + await waitForPromises(); + }); + + it('removes loading state', () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findModal().props('actionCancel').attributes.loading).toBe(false); + }); + + it('user interface is partially updated', () => { + const { evict, gc } = apolloCache; + + expect(evict).toHaveBeenCalledTimes(1); + expect(evict).toHaveBeenCalledWith({ + id: expect.stringContaining(mockId1), + }); + + expect(gc).toHaveBeenCalledTimes(1); + }); + + it('emits deletion confirmation', () => { + expect(wrapper.emitted('deleted')).toEqual([[{ message: expect.stringContaining('1') }]]); + }); + + it('alert is called', () => { + expect(createAlert).toHaveBeenCalled(); + expect(createAlert).toHaveBeenCalledWith({ + message: expect.any(String), + captureError: true, + error: expect.any(Error), + }); + }); + + it('modal is hidden', () => { + expect(mockHideModal).toHaveBeenCalledTimes(1); + }); + }); + + describe('when deletion fails', () => { + beforeEach(async () => { + bulkRunnerDeleteHandler.mockRejectedValue(new Error('error!')); + + confirmDeletion(); + await waitForPromises(); + }); + + it('resolves loading state', () => { + expect(findModal().props('actionPrimary').attributes.loading).toBe(false); + expect(findModal().props('actionCancel').attributes.loading).toBe(false); + }); + + it('user interface is not updated', () => { + const { evict, gc } = apolloCache; + + expect(evict).not.toHaveBeenCalled(); + expect(gc).not.toHaveBeenCalled(); + expect(mockState.localMutations.clearChecked).not.toHaveBeenCalled(); + }); + + it('does not emit deletion confirmation', () => { + expect(wrapper.emitted('deleted')).toBeUndefined(); + }); + + it('alert is called', () => { + expect(createAlert).toHaveBeenCalled(); + expect(createAlert).toHaveBeenCalledWith({ + message: expect.any(String), + captureError: true, + error: expect.any(Error), + }); + }); + + it('modal is hidden', () => { + expect(mockHideModal).toHaveBeenCalledTimes(1); + }); + }); + }); +}); |