diff options
Diffstat (limited to 'spec/frontend/ide/components/new_dropdown')
4 files changed, 436 insertions, 0 deletions
diff --git a/spec/frontend/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js new file mode 100644 index 00000000000..3c611b7de8f --- /dev/null +++ b/spec/frontend/ide/components/new_dropdown/button_spec.js @@ -0,0 +1,65 @@ +import Vue from 'vue'; +import mountComponent from 'helpers/vue_mount_component_helper'; +import Button from '~/ide/components/new_dropdown/button.vue'; + +describe('IDE new entry dropdown button component', () => { + let Component; + let vm; + + beforeAll(() => { + Component = Vue.extend(Button); + }); + + beforeEach(() => { + vm = mountComponent(Component, { + label: 'Testing', + icon: 'doc-new', + }); + + jest.spyOn(vm, '$emit').mockImplementation(() => {}); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders button with label', () => { + expect(vm.$el.textContent).toContain('Testing'); + }); + + it('renders icon', () => { + expect(vm.$el.querySelector('.ic-doc-new')).not.toBe(null); + }); + + it('emits click event', () => { + vm.$el.click(); + + expect(vm.$emit).toHaveBeenCalledWith('click'); + }); + + it('hides label if showLabel is false', done => { + vm.showLabel = false; + + vm.$nextTick(() => { + expect(vm.$el.textContent).not.toContain('Testing'); + + done(); + }); + }); + + describe('tooltipTitle', () => { + it('returns empty string when showLabel is true', () => { + expect(vm.tooltipTitle).toBe(''); + }); + + it('returns label', done => { + vm.showLabel = false; + + vm.$nextTick(() => { + expect(vm.tooltipTitle).toBe('Testing'); + + done(); + }); + }); + }); +}); diff --git a/spec/frontend/ide/components/new_dropdown/index_spec.js b/spec/frontend/ide/components/new_dropdown/index_spec.js new file mode 100644 index 00000000000..00781c16609 --- /dev/null +++ b/spec/frontend/ide/components/new_dropdown/index_spec.js @@ -0,0 +1,84 @@ +import Vue from 'vue'; +import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import store from '~/ide/stores'; +import newDropdown from '~/ide/components/new_dropdown/index.vue'; +import { resetStore } from '../../helpers'; + +describe('new dropdown component', () => { + let vm; + + beforeEach(() => { + const component = Vue.extend(newDropdown); + + vm = createComponentWithStore(component, store, { + branch: 'master', + path: '', + mouseOver: false, + type: 'tree', + }); + + vm.$store.state.currentProjectId = 'abcproject'; + vm.$store.state.path = ''; + vm.$store.state.trees['abcproject/mybranch'] = { + tree: [], + }; + + vm.$mount(); + + jest.spyOn(vm.$refs.newModal, 'open').mockImplementation(() => {}); + }); + + afterEach(() => { + vm.$destroy(); + + resetStore(vm.$store); + }); + + it('renders new file, upload and new directory links', () => { + const buttons = vm.$el.querySelectorAll('.dropdown-menu button'); + + expect(buttons[0].textContent.trim()).toBe('New file'); + expect(buttons[1].textContent.trim()).toBe('Upload file'); + expect(buttons[2].textContent.trim()).toBe('New directory'); + }); + + describe('createNewItem', () => { + it('opens modal for a blob when new file is clicked', () => { + vm.$el.querySelectorAll('.dropdown-menu button')[0].click(); + + expect(vm.$refs.newModal.open).toHaveBeenCalledWith('blob', ''); + }); + + it('opens modal for a tree when new directory is clicked', () => { + vm.$el.querySelectorAll('.dropdown-menu button')[2].click(); + + expect(vm.$refs.newModal.open).toHaveBeenCalledWith('tree', ''); + }); + }); + + describe('isOpen', () => { + it('scrolls dropdown into view', done => { + jest.spyOn(vm.$refs.dropdownMenu, 'scrollIntoView').mockImplementation(() => {}); + + vm.isOpen = true; + + setImmediate(() => { + expect(vm.$refs.dropdownMenu.scrollIntoView).toHaveBeenCalledWith({ + block: 'nearest', + }); + + done(); + }); + }); + }); + + describe('delete entry', () => { + it('calls delete action', () => { + jest.spyOn(vm, 'deleteEntry').mockImplementation(() => {}); + + vm.$el.querySelectorAll('.dropdown-menu button')[4].click(); + + expect(vm.deleteEntry).toHaveBeenCalledWith(''); + }); + }); +}); diff --git a/spec/frontend/ide/components/new_dropdown/modal_spec.js b/spec/frontend/ide/components/new_dropdown/modal_spec.js new file mode 100644 index 00000000000..62a59a76bf4 --- /dev/null +++ b/spec/frontend/ide/components/new_dropdown/modal_spec.js @@ -0,0 +1,175 @@ +import Vue from 'vue'; +import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { createStore } from '~/ide/stores'; +import modal from '~/ide/components/new_dropdown/modal.vue'; +import createFlash from '~/flash'; + +jest.mock('~/flash'); + +describe('new file modal component', () => { + const Component = Vue.extend(modal); + let vm; + + afterEach(() => { + vm.$destroy(); + }); + + describe.each` + entryType | modalTitle | btnTitle | showsFileTemplates + ${'tree'} | ${'Create new directory'} | ${'Create directory'} | ${false} + ${'blob'} | ${'Create new file'} | ${'Create file'} | ${true} + `('$entryType', ({ entryType, modalTitle, btnTitle, showsFileTemplates }) => { + beforeEach(done => { + const store = createStore(); + + vm = createComponentWithStore(Component, store).$mount(); + vm.open(entryType); + vm.name = 'testing'; + + vm.$nextTick(done); + }); + + afterEach(() => { + vm.close(); + }); + + it(`sets modal title as ${entryType}`, () => { + expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle); + }); + + it(`sets button label as ${entryType}`, () => { + expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle); + }); + + it(`sets form label as ${entryType}`, () => { + expect(document.querySelector('.label-bold').textContent.trim()).toBe('Name'); + }); + + it(`shows file templates: ${showsFileTemplates}`, () => { + const templateFilesEl = document.querySelector('.file-templates'); + expect(Boolean(templateFilesEl)).toBe(showsFileTemplates); + }); + }); + + describe('rename entry', () => { + beforeEach(() => { + const store = createStore(); + store.state.entries = { + 'test-path': { + name: 'test', + type: 'blob', + path: 'test-path', + }, + }; + + vm = createComponentWithStore(Component, store).$mount(); + }); + + it.each` + entryType | modalTitle | btnTitle + ${'tree'} | ${'Rename folder'} | ${'Rename folder'} + ${'blob'} | ${'Rename file'} | ${'Rename file'} + `( + 'renders title and button for renaming $entryType', + ({ entryType, modalTitle, btnTitle }, done) => { + vm.$store.state.entries['test-path'].type = entryType; + vm.open('rename', 'test-path'); + + vm.$nextTick(() => { + expect(document.querySelector('.modal-title').textContent.trim()).toBe(modalTitle); + expect(document.querySelector('.btn-success').textContent.trim()).toBe(btnTitle); + + done(); + }); + }, + ); + + describe('entryName', () => { + it('returns entries name', () => { + vm.open('rename', 'test-path'); + + expect(vm.entryName).toBe('test-path'); + }); + + it('does not reset entryName to its old value if empty', () => { + vm.entryName = 'hello'; + vm.entryName = ''; + + expect(vm.entryName).toBe(''); + }); + }); + + describe('open', () => { + it('sets entryName to path provided if modalType is rename', () => { + vm.open('rename', 'test-path'); + + expect(vm.entryName).toBe('test-path'); + }); + + it("appends '/' to the path if modalType isn't rename", () => { + vm.open('blob', 'test-path'); + + expect(vm.entryName).toBe('test-path/'); + }); + + it('leaves entryName blank if no path is provided', () => { + vm.open('blob'); + + expect(vm.entryName).toBe(''); + }); + }); + }); + + describe('submitForm', () => { + let store; + + beforeEach(() => { + store = createStore(); + store.state.entries = { + 'test-path/test': { + name: 'test', + deleted: false, + }, + }; + + vm = createComponentWithStore(Component, store).$mount(); + }); + + it('throws an error when target entry exists', () => { + vm.open('rename', 'test-path/test'); + + expect(createFlash).not.toHaveBeenCalled(); + + vm.submitForm(); + + expect(createFlash).toHaveBeenCalledWith( + 'The name "test-path/test" is already taken in this directory.', + 'alert', + expect.anything(), + null, + false, + true, + ); + }); + + it('does not throw error when target entry does not exist', () => { + jest.spyOn(vm, 'renameEntry').mockImplementation(); + + vm.open('rename', 'test-path/test'); + vm.entryName = 'test-path/test2'; + vm.submitForm(); + + expect(createFlash).not.toHaveBeenCalled(); + }); + + it('removes leading/trailing found in the new name', () => { + vm.open('rename', 'test-path/test'); + + vm.entryName = 'test-path /test'; + + vm.submitForm(); + + expect(vm.entryName).toBe('test-path/test'); + }); + }); +}); diff --git a/spec/frontend/ide/components/new_dropdown/upload_spec.js b/spec/frontend/ide/components/new_dropdown/upload_spec.js new file mode 100644 index 00000000000..a418fdeb572 --- /dev/null +++ b/spec/frontend/ide/components/new_dropdown/upload_spec.js @@ -0,0 +1,112 @@ +import Vue from 'vue'; +import createComponent from 'helpers/vue_mount_component_helper'; +import upload from '~/ide/components/new_dropdown/upload.vue'; + +describe('new dropdown upload', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(upload); + + vm = createComponent(Component, { + path: '', + }); + + vm.entryName = 'testing'; + + jest.spyOn(vm, '$emit'); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('openFile', () => { + it('calls for each file', () => { + const files = ['test', 'test2', 'test3']; + + jest.spyOn(vm, 'readFile').mockImplementation(() => {}); + jest.spyOn(vm.$refs.fileUpload, 'files', 'get').mockReturnValue(files); + + vm.openFile(); + + expect(vm.readFile.mock.calls.length).toBe(3); + + files.forEach((file, i) => { + expect(vm.readFile.mock.calls[i]).toEqual([file]); + }); + }); + }); + + describe('readFile', () => { + beforeEach(() => { + jest.spyOn(FileReader.prototype, 'readAsDataURL').mockImplementation(() => {}); + }); + + it('calls readAsDataURL for all files', () => { + const file = { + type: 'images/png', + }; + + vm.readFile(file); + + expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file); + }); + }); + + describe('createFile', () => { + const textTarget = { + result: 'base64,cGxhaW4gdGV4dA==', + }; + const binaryTarget = { + result: 'base64,w4I=', + }; + const textFile = new File(['plain text'], 'textFile'); + + const binaryFile = { + name: 'binaryFile', + type: 'image/png', + }; + + beforeEach(() => { + jest.spyOn(FileReader.prototype, 'readAsText'); + }); + + it('calls readAsText and creates file in plain text (without encoding) if the file content is plain text', done => { + const waitForCreate = new Promise(resolve => vm.$on('create', resolve)); + + vm.createFile(textTarget, textFile); + + expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(textFile); + + waitForCreate + .then(() => { + expect(vm.$emit).toHaveBeenCalledWith('create', { + name: textFile.name, + type: 'blob', + content: 'plain text', + base64: false, + binary: false, + rawPath: '', + }); + }) + .then(done) + .catch(done.fail); + }); + + it('splits content on base64 if binary', () => { + vm.createFile(binaryTarget, binaryFile); + + expect(FileReader.prototype.readAsText).not.toHaveBeenCalledWith(textFile); + + expect(vm.$emit).toHaveBeenCalledWith('create', { + name: binaryFile.name, + type: 'blob', + content: binaryTarget.result.split('base64,')[1], + base64: true, + binary: true, + rawPath: binaryTarget.result, + }); + }); + }); +}); |