diff options
Diffstat (limited to 'spec/frontend/ide/components/commit_sidebar/form_spec.js')
-rw-r--r-- | spec/frontend/ide/components/commit_sidebar/form_spec.js | 395 |
1 files changed, 207 insertions, 188 deletions
diff --git a/spec/frontend/ide/components/commit_sidebar/form_spec.js b/spec/frontend/ide/components/commit_sidebar/form_spec.js index abd7e3bb8fc..2b567816ce8 100644 --- a/spec/frontend/ide/components/commit_sidebar/form_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/form_spec.js @@ -1,11 +1,12 @@ +import { GlModal } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; -import { getByText } from '@testing-library/dom'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { projectData } from 'jest/ide/mock_data'; +import { stubComponent } from 'helpers/stub_component'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; import waitForPromises from 'helpers/wait_for_promises'; -import { createStore } from '~/ide/stores'; -import consts from '~/ide/stores/modules/commit/constants'; +import { projectData } from 'jest/ide/mock_data'; import CommitForm from '~/ide/components/commit_sidebar/form.vue'; +import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue'; import { leftSidebarViews } from '~/ide/constants'; import { createCodeownersCommitError, @@ -13,258 +14,287 @@ import { createBranchChangedCommitError, branchAlreadyExistsCommitError, } from '~/ide/lib/errors'; +import { createStore } from '~/ide/stores'; +import { COMMIT_TO_NEW_BRANCH } from '~/ide/stores/modules/commit/constants'; describe('IDE commit form', () => { - const Component = Vue.extend(CommitForm); - let vm; + let wrapper; let store; - const beginCommitButton = () => vm.$el.querySelector('[data-testid="begin-commit-button"]'); + const createComponent = () => { + wrapper = shallowMount(CommitForm, { + store, + directives: { + GlTooltip: createMockDirective(), + }, + stubs: { + GlModal: stubComponent(GlModal), + }, + }); + }; + + const setLastCommitMessage = (msg) => { + store.state.lastCommitMsg = msg; + }; + const goToCommitView = () => { + store.state.currentActivityView = leftSidebarViews.commit.name; + }; + const goToEditView = () => { + store.state.currentActivityView = leftSidebarViews.edit.name; + }; + const findBeginCommitButton = () => wrapper.find('[data-testid="begin-commit-button"]'); + const findBeginCommitButtonTooltip = () => + wrapper.find('[data-testid="begin-commit-button-tooltip"]'); + const findBeginCommitButtonData = () => ({ + disabled: findBeginCommitButton().props('disabled'), + tooltip: getBinding(findBeginCommitButtonTooltip().element, 'gl-tooltip').value.title, + }); + const findCommitButton = () => wrapper.find('[data-testid="commit-button"]'); + const findCommitButtonTooltip = () => wrapper.find('[data-testid="commit-button-tooltip"]'); + const findCommitButtonData = () => ({ + disabled: findCommitButton().props('disabled'), + tooltip: getBinding(findCommitButtonTooltip().element, 'gl-tooltip').value.title, + }); + const clickCommitButton = () => findCommitButton().vm.$emit('click'); + const findForm = () => wrapper.find('form'); + const submitForm = () => findForm().trigger('submit'); + const findCommitMessageInput = () => wrapper.find(CommitMessageField); + const setCommitMessageInput = (val) => findCommitMessageInput().vm.$emit('input', val); + const findDiscardDraftButton = () => wrapper.find('[data-testid="discard-draft"]'); beforeEach(() => { store = createStore(); - store.state.changedFiles.push('test'); + store.state.stagedFiles.push('test'); store.state.currentProjectId = 'abcproject'; store.state.currentBranchId = 'master'; - Vue.set(store.state.projects, 'abcproject', { ...projectData }); - - vm = createComponentWithStore(Component, store).$mount(); + Vue.set(store.state.projects, 'abcproject', { + ...projectData, + userPermissions: { pushCode: true }, + }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); - it('enables begin commit button when there are changes', () => { - expect(beginCommitButton()).not.toHaveAttr('disabled'); - }); + // Notes: + // - When there are no changes, there is no commit button so there's nothing to test :) + describe.each` + desc | stagedFiles | userPermissions | viewFn | buttonFn | disabled | tooltip + ${'when there are no changes'} | ${[]} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${''} + ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToEditView} | ${findBeginCommitButtonData} | ${false} | ${''} + ${'when there are changes'} | ${['test']} | ${{ pushCode: true }} | ${goToCommitView} | ${findCommitButtonData} | ${false} | ${''} + ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToEditView} | ${findBeginCommitButtonData} | ${true} | ${CommitForm.MSG_CANNOT_PUSH_CODE} + ${'when user cannot push'} | ${['test']} | ${{ pushCode: false }} | ${goToCommitView} | ${findCommitButtonData} | ${true} | ${CommitForm.MSG_CANNOT_PUSH_CODE} + `('$desc', ({ stagedFiles, userPermissions, viewFn, buttonFn, disabled, tooltip }) => { + beforeEach(async () => { + store.state.stagedFiles = stagedFiles; + store.state.projects.abcproject.userPermissions = userPermissions; + + createComponent(); + }); - it('disables begin commit button when there are no changes', async () => { - store.state.changedFiles = []; - await vm.$nextTick(); + it(`at view=${viewFn.name}, ${buttonFn.name} has disabled=${disabled} tooltip=${tooltip}`, async () => { + viewFn(); - expect(beginCommitButton()).toHaveAttr('disabled'); + await wrapper.vm.$nextTick(); + + expect(buttonFn()).toEqual({ + disabled, + tooltip, + }); + }); }); - describe('compact', () => { - beforeEach(() => { - vm.isCompact = true; + describe('on edit tab', () => { + beforeEach(async () => { + // Test that we react to switching to compact view. + goToCommitView(); + + createComponent(); - return vm.$nextTick(); + goToEditView(); + + await wrapper.vm.$nextTick(); }); it('renders commit button in compact mode', () => { - expect(beginCommitButton()).not.toBeNull(); - expect(beginCommitButton().textContent).toContain('Commit'); + expect(findBeginCommitButton().exists()).toBe(true); + expect(findBeginCommitButton().text()).toBe('Commit…'); }); it('does not render form', () => { - expect(vm.$el.querySelector('form')).toBeNull(); + expect(findForm().exists()).toBe(false); }); it('renders overview text', () => { - vm.$store.state.stagedFiles.push('test'); - - return vm.$nextTick(() => { - expect(vm.$el.querySelector('p').textContent).toContain('1 changed file'); - }); + expect(wrapper.find('p').text()).toBe('1 changed file'); }); - it('shows form when clicking commit button', () => { - beginCommitButton().click(); - - return vm.$nextTick(() => { - expect(vm.$el.querySelector('form')).not.toBeNull(); - }); - }); + it('when begin commit button is clicked, shows form', async () => { + findBeginCommitButton().vm.$emit('click'); - it('toggles activity bar view when clicking commit button', () => { - beginCommitButton().click(); + await wrapper.vm.$nextTick(); - return vm.$nextTick(() => { - expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name); - }); + expect(findForm().exists()).toBe(true); }); - it('collapses if lastCommitMsg is set to empty and current view is not commit view', async () => { - store.state.lastCommitMsg = 'abc'; - store.state.currentActivityView = leftSidebarViews.edit.name; - await vm.$nextTick(); - - // if commit message is set, form is uncollapsed - expect(vm.isCompact).toBe(false); + it('when begin commit button is clicked, sets activity view', async () => { + findBeginCommitButton().vm.$emit('click'); - store.state.lastCommitMsg = ''; - await vm.$nextTick(); + await wrapper.vm.$nextTick(); - // collapsed when set to empty - expect(vm.isCompact).toBe(true); + expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name); }); - it('collapses if in commit view but there are no changes and vice versa', async () => { - store.state.currentActivityView = leftSidebarViews.commit.name; - await vm.$nextTick(); + it('collapses if lastCommitMsg is set to empty and current view is not commit view', async () => { + // Test that it expands when lastCommitMsg is set + setLastCommitMessage('test'); + goToEditView(); - // expanded by default if there are changes - expect(vm.isCompact).toBe(false); + await wrapper.vm.$nextTick(); - store.state.changedFiles = []; - await vm.$nextTick(); + expect(findForm().exists()).toBe(true); - expect(vm.isCompact).toBe(true); + // Now test that it collapses when lastCommitMsg is cleared + setLastCommitMessage(''); - store.state.changedFiles.push('test'); - await vm.$nextTick(); + await wrapper.vm.$nextTick(); - // uncollapsed once again - expect(vm.isCompact).toBe(false); + expect(findForm().exists()).toBe(false); }); + }); - it('collapses if switched from commit view to edit view and vice versa', async () => { - store.state.currentActivityView = leftSidebarViews.edit.name; - await vm.$nextTick(); - - expect(vm.isCompact).toBe(true); + describe('on commit tab when window height is less than MAX_WINDOW_HEIGHT', () => { + let oldHeight; - store.state.currentActivityView = leftSidebarViews.commit.name; - await vm.$nextTick(); + beforeEach(async () => { + oldHeight = window.innerHeight; + window.innerHeight = 700; - expect(vm.isCompact).toBe(false); + createComponent(); - store.state.currentActivityView = leftSidebarViews.edit.name; - await vm.$nextTick(); + goToCommitView(); - expect(vm.isCompact).toBe(true); + await wrapper.vm.$nextTick(); }); - describe('when window height is less than MAX_WINDOW_HEIGHT', () => { - let oldHeight; - - beforeEach(() => { - oldHeight = window.innerHeight; - window.innerHeight = 700; - }); + afterEach(() => { + window.innerHeight = oldHeight; + }); - afterEach(() => { - window.innerHeight = oldHeight; - }); + it('stays collapsed if changes are added or removed', async () => { + expect(findForm().exists()).toBe(false); - it('stays collapsed when switching from edit view to commit view and back', async () => { - store.state.currentActivityView = leftSidebarViews.edit.name; - await vm.$nextTick(); + store.state.stagedFiles = []; + await wrapper.vm.$nextTick(); - expect(vm.isCompact).toBe(true); + expect(findForm().exists()).toBe(false); - store.state.currentActivityView = leftSidebarViews.commit.name; - await vm.$nextTick(); + store.state.stagedFiles.push('test'); + await wrapper.vm.$nextTick(); - expect(vm.isCompact).toBe(true); + expect(findForm().exists()).toBe(false); + }); + }); - store.state.currentActivityView = leftSidebarViews.edit.name; - await vm.$nextTick(); + describe('on commit tab', () => { + beforeEach(async () => { + // Test that the component reacts to switching to full view + goToEditView(); - expect(vm.isCompact).toBe(true); - }); + createComponent(); - it('stays uncollapsed if changes are added or removed', async () => { - store.state.currentActivityView = leftSidebarViews.commit.name; - await vm.$nextTick(); + goToCommitView(); - expect(vm.isCompact).toBe(true); + await wrapper.vm.$nextTick(); + }); - store.state.changedFiles = []; - await vm.$nextTick(); + it('shows form', () => { + expect(findForm().exists()).toBe(true); + }); - expect(vm.isCompact).toBe(true); + it('hides begin commit button', () => { + expect(findBeginCommitButton().exists()).toBe(false); + }); - store.state.changedFiles.push('test'); - await vm.$nextTick(); + describe('when no changed files', () => { + beforeEach(async () => { + store.state.stagedFiles = []; + await wrapper.vm.$nextTick(); + }); - expect(vm.isCompact).toBe(true); + it('hides form', () => { + expect(findForm().exists()).toBe(false); }); - it('uncollapses when clicked on Commit button in the edit view', async () => { - store.state.currentActivityView = leftSidebarViews.edit.name; - beginCommitButton().click(); - await waitForPromises(); + it('expands again when staged files are added', async () => { + store.state.stagedFiles.push('test'); + await wrapper.vm.$nextTick(); - expect(vm.isCompact).toBe(false); + expect(findForm().exists()).toBe(true); }); }); - }); - describe('full', () => { - beforeEach(() => { - vm.isCompact = false; + it('updates commitMessage in store on input', async () => { + setCommitMessageInput('testing commit message'); - return vm.$nextTick(); + await wrapper.vm.$nextTick(); + + expect(store.state.commit.commitMessage).toBe('testing commit message'); }); - it('updates commitMessage in store on input', () => { - const textarea = vm.$el.querySelector('textarea'); + describe('discard draft button', () => { + it('hidden when commitMessage is empty', () => { + expect(findDiscardDraftButton().exists()).toBe(false); + }); - textarea.value = 'testing commit message'; + it('resets commitMessage when clicking discard button', async () => { + setCommitMessageInput('testing commit message'); - textarea.dispatchEvent(new Event('input')); + await wrapper.vm.$nextTick(); - return vm.$nextTick().then(() => { - expect(vm.$store.state.commit.commitMessage).toBe('testing commit message'); - }); - }); + expect(findCommitMessageInput().props('text')).toBe('testing commit message'); - it('updating currentActivityView not to commit view sets compact mode', () => { - store.state.currentActivityView = 'a'; + // Test that commitMessage is cleared on click + findDiscardDraftButton().vm.$emit('click'); - return vm.$nextTick(() => { - expect(vm.isCompact).toBe(true); + await wrapper.vm.$nextTick(); + + expect(findCommitMessageInput().props('text')).toBe(''); }); }); - it('always opens itself in full view current activity view is not commit view when clicking commit button', () => { - beginCommitButton().click(); + describe('when submitting', () => { + beforeEach(async () => { + goToEditView(); - return vm.$nextTick(() => { - expect(store.state.currentActivityView).toBe(leftSidebarViews.commit.name); - expect(vm.isCompact).toBe(false); - }); - }); + createComponent(); - describe('discard draft button', () => { - it('hidden when commitMessage is empty', () => { - expect(vm.$el.querySelector('.btn-default').textContent).toContain('Collapse'); - }); + goToCommitView(); + + await wrapper.vm.$nextTick(); - it('resets commitMessage when clicking discard button', () => { - vm.$store.state.commit.commitMessage = 'testing commit message'; - - return vm - .$nextTick() - .then(() => { - vm.$el.querySelector('.btn-default').click(); - }) - .then(() => vm.$nextTick()) - .then(() => { - expect(vm.$store.state.commit.commitMessage).not.toBe('testing commit message'); - }); + setCommitMessageInput('testing commit message'); + + await wrapper.vm.$nextTick(); + + jest.spyOn(store, 'dispatch').mockResolvedValue(); }); - }); - describe('when submitting', () => { - beforeEach(() => { - jest.spyOn(vm, 'commitChanges'); + it.each([clickCommitButton, submitForm])('when %p, commits changes', (fn) => { + fn(); - vm.$store.state.stagedFiles.push('test'); - vm.$store.state.commit.commitMessage = 'testing commit message'; + expect(store.dispatch).toHaveBeenCalledWith('commit/commitChanges', undefined); }); - it('calls commitChanges', () => { - vm.commitChanges.mockResolvedValue({ success: true }); + it('when cannot push code, submitting does nothing', async () => { + store.state.projects.abcproject.userPermissions.pushCode = false; + await wrapper.vm.$nextTick(); - return vm.$nextTick().then(() => { - vm.$el.querySelector('.btn-success').click(); + submitForm(); - expect(vm.commitChanges).toHaveBeenCalled(); - }); + expect(store.dispatch).not.toHaveBeenCalled(); }); it.each` @@ -272,31 +302,32 @@ describe('IDE commit form', () => { ${() => createCodeownersCommitError('test message')} | ${{ actionPrimary: { text: 'Create new branch' } }} ${createUnexpectedCommitError} | ${{ actionPrimary: null }} `('opens error modal if commitError with $error', async ({ createError, props }) => { - jest.spyOn(vm.$refs.commitErrorModal, 'show'); + const modal = wrapper.find(GlModal); + modal.vm.show = jest.fn(); const error = createError(); store.state.commit.commitError = error; - await vm.$nextTick(); + await wrapper.vm.$nextTick(); - expect(vm.$refs.commitErrorModal.show).toHaveBeenCalled(); - expect(vm.$refs.commitErrorModal).toMatchObject({ + expect(modal.vm.show).toHaveBeenCalled(); + expect(modal.props()).toMatchObject({ actionCancel: { text: 'Cancel' }, ...props, }); // Because of the legacy 'mountComponent' approach here, the only way to // test the text of the modal is by viewing the content of the modal added to the document. - expect(document.body).toHaveText(error.messageHTML); + expect(modal.html()).toContain(error.messageHTML); }); }); describe('with error modal with primary', () => { beforeEach(() => { - jest.spyOn(vm.$store, 'dispatch').mockReturnValue(Promise.resolve()); + jest.spyOn(store, 'dispatch').mockResolvedValue(); }); const commitActions = [ - ['commit/updateCommitAction', consts.COMMIT_TO_NEW_BRANCH], + ['commit/updateCommitAction', COMMIT_TO_NEW_BRANCH], ['commit/commitChanges'], ]; @@ -310,27 +341,15 @@ describe('IDE commit form', () => { async ({ commitError, expectedActions }) => { store.state.commit.commitError = commitError('test message'); - await vm.$nextTick(); + await wrapper.vm.$nextTick(); - getByText(document.body, 'Create new branch').click(); + wrapper.find(GlModal).vm.$emit('ok'); await waitForPromises(); - expect(vm.$store.dispatch.mock.calls).toEqual(expectedActions); + expect(store.dispatch.mock.calls).toEqual(expectedActions); }, ); }); }); - - describe('commitButtonText', () => { - it('returns commit text when staged files exist', () => { - vm.$store.state.stagedFiles.push('testing'); - - expect(vm.commitButtonText).toBe('Commit'); - }); - - it('returns stage & commit text when staged files do not exist', () => { - expect(vm.commitButtonText).toBe('Stage & Commit'); - }); - }); }); |