import { shallowMount } from '@vue/test-utils'; import { GlIcon } from '@gitlab/ui'; import UploadDropzone from '~/vue_shared/components/upload_dropzone/upload_dropzone.vue'; jest.mock('~/flash'); describe('Upload dropzone component', () => { let wrapper; const mockDragEvent = ({ types = ['Files'], files = [] }) => { return { dataTransfer: { types, files } }; }; const findDropzoneCard = () => wrapper.find('.upload-dropzone-card'); const findDropzoneArea = () => wrapper.find('[data-testid="dropzone-area"]'); const findIcon = () => wrapper.find(GlIcon); function createComponent({ slots = {}, data = {}, props = {} } = {}) { wrapper = shallowMount(UploadDropzone, { slots, propsData: { displayAsCard: true, ...props, }, data() { return data; }, }); } afterEach(() => { wrapper.destroy(); }); describe('when slot provided', () => { it('renders dropzone with slot content', () => { createComponent({ slots: { default: ['
dropzone slot
'], }, }); expect(wrapper.element).toMatchSnapshot(); }); }); describe('when no slot provided', () => { it('renders default dropzone card', () => { createComponent(); expect(wrapper.element).toMatchSnapshot(); }); it('triggers click event on file input element when clicked', () => { createComponent(); const clickSpy = jest.spyOn(wrapper.find('input').element, 'click'); findDropzoneCard().trigger('click'); expect(clickSpy).toHaveBeenCalled(); }); }); describe('when dragging', () => { it.each` description | eventPayload ${'is empty'} | ${{}} ${'contains text'} | ${mockDragEvent({ types: ['text'] })} ${'contains files and text'} | ${mockDragEvent({ types: ['Files', 'text'] })} ${'contains files'} | ${mockDragEvent({ types: ['Files'] })} `('renders correct template when drag event $description', ({ eventPayload }) => { createComponent(); wrapper.trigger('dragenter', eventPayload); return wrapper.vm.$nextTick().then(() => { expect(wrapper.element).toMatchSnapshot(); }); }); it('renders correct template when dragging stops', () => { createComponent(); wrapper.trigger('dragenter'); return wrapper.vm .$nextTick() .then(() => { wrapper.trigger('dragleave'); return wrapper.vm.$nextTick(); }) .then(() => { expect(wrapper.element).toMatchSnapshot(); }); }); }); describe('when dropping', () => { it('emits upload event', () => { createComponent(); const mockFile = { name: 'test', type: 'image/jpg' }; const mockEvent = mockDragEvent({ files: [mockFile] }); wrapper.trigger('dragenter', mockEvent); return wrapper.vm .$nextTick() .then(() => { wrapper.trigger('drop', mockEvent); return wrapper.vm.$nextTick(); }) .then(() => { expect(wrapper.emitted().change[0]).toEqual([[mockFile]]); }); }); }); describe('ondrop', () => { const mockData = { dragCounter: 1, isDragDataValid: true }; describe('when drag data is valid', () => { it('emits upload event for valid files', () => { createComponent({ data: mockData }); const mockFile = { type: 'image/jpg' }; const mockEvent = mockDragEvent({ files: [mockFile] }); wrapper.vm.ondrop(mockEvent); expect(wrapper.emitted().change[0]).toEqual([[mockFile]]); }); it('emits error event when files are invalid', () => { createComponent({ data: mockData }); const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] }); wrapper.vm.ondrop(mockEvent); expect(wrapper.emitted()).toHaveProperty('error'); }); it('allows validation function to be overwritten', () => { createComponent({ data: mockData, props: { isFileValid: () => true } }); const mockEvent = mockDragEvent({ files: [{ type: 'audio/midi' }] }); wrapper.vm.ondrop(mockEvent); expect(wrapper.emitted()).not.toHaveProperty('error'); }); }); }); it('applies correct classes when displaying as a standalone item', () => { createComponent({ props: { displayAsCard: false } }); expect(findDropzoneArea().classes()).not.toContain('gl-flex-direction-column'); expect(findIcon().classes()).toEqual(['gl-mr-3', 'gl-text-gray-500']); expect(findIcon().props('size')).toBe(16); }); it('applies correct classes when displaying in card mode', () => { createComponent({ props: { displayAsCard: true } }); expect(findDropzoneArea().classes()).toContain('gl-flex-direction-column'); expect(findIcon().classes()).toEqual(['gl-mb-2']); expect(findIcon().props('size')).toBe(24); }); it('correctly overrides description and drop messages', () => { createComponent({ props: { dropToStartMessage: 'Test drop-to-start message.', validFileMimetypes: ['image/jpg', 'image/jpeg'], }, slots: { 'upload-text': 'Test %{linkStart}description%{linkEnd} message.', }, }); expect(wrapper.element).toMatchSnapshot(); }); });