From 077b0a79d52753d020280ed8d58f97f8207b42de Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Mon, 3 Oct 2022 12:08:27 +0000 Subject: Add latest changes from gitlab-org/gitlab@master --- .../ide/components/commit_sidebar/actions_spec.js | 65 ++++----- .../components/commit_sidebar/list_item_spec.js | 113 +++++++------- .../commit_sidebar/message_field_spec.js | 128 ++++++++-------- .../components/commit_sidebar/radio_group_spec.js | 135 ++++++++--------- .../frontend/ide/components/file_row_extra_spec.js | 140 +++++++++--------- .../ide/components/file_templates/bar_spec.js | 71 ++++----- .../ide/components/jobs/detail/description_spec.js | 35 +++-- spec/frontend/ide/components/jobs/detail_spec.js | 162 +++++++++------------ spec/frontend/ide/components/jobs/item_spec.js | 30 ++-- .../ide/components/new_dropdown/button_spec.js | 65 +++++---- .../ide/components/new_dropdown/upload_spec.js | 71 +++++---- .../ide/components/shared/tokened_input_spec.js | 135 ++++++++--------- 12 files changed, 531 insertions(+), 619 deletions(-) (limited to 'spec/frontend/ide') diff --git a/spec/frontend/ide/components/commit_sidebar/actions_spec.js b/spec/frontend/ide/components/commit_sidebar/actions_spec.js index c9425f6c9cd..dc103fec5d0 100644 --- a/spec/frontend/ide/components/commit_sidebar/actions_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/actions_spec.js @@ -1,7 +1,7 @@ import Vue, { nextTick } from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; import { projectData, branches } from 'jest/ide/mock_data'; -import commitActions from '~/ide/components/commit_sidebar/actions.vue'; +import CommitActions from '~/ide/components/commit_sidebar/actions.vue'; import { createStore } from '~/ide/stores'; import { COMMIT_TO_NEW_BRANCH, @@ -18,32 +18,27 @@ const BRANCH_REGULAR_NO_ACCESS = 'regular/no-access'; describe('IDE commit sidebar actions', () => { let store; - let vm; + let wrapper; const createComponent = ({ hasMR = false, currentBranchId = 'main', emptyRepo = false } = {}) => { - const Component = Vue.extend(commitActions); - - vm = createComponentWithStore(Component, store); - - vm.$store.state.currentBranchId = currentBranchId; - vm.$store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = currentBranchId; + store.state.currentProjectId = 'abcproject'; const proj = { ...projectData }; proj.branches[currentBranchId] = branches.find((branch) => branch.name === currentBranchId); proj.empty_repo = emptyRepo; - Vue.set(vm.$store.state.projects, 'abcproject', proj); + Vue.set(store.state.projects, 'abcproject', proj); if (hasMR) { - vm.$store.state.currentMergeRequestId = '1'; - vm.$store.state.projects[store.state.currentProjectId].mergeRequests[ + store.state.currentMergeRequestId = '1'; + store.state.projects[store.state.currentProjectId].mergeRequests[ store.state.currentMergeRequestId ] = { foo: 'bar' }; } - vm.$mount(); - - return vm; + wrapper = mount(CommitActions, { store }); + return wrapper; }; beforeEach(() => { @@ -52,17 +47,16 @@ describe('IDE commit sidebar actions', () => { }); afterEach(() => { - vm.$destroy(); - vm = null; + wrapper.destroy(); }); - const findText = () => vm.$el.textContent; - const findRadios = () => Array.from(vm.$el.querySelectorAll('input[type="radio"]')); + const findText = () => wrapper.text(); + const findRadios = () => wrapper.findAll('input[type="radio"]'); it('renders 2 groups', () => { createComponent(); - expect(findRadios().length).toBe(2); + expect(findRadios()).toHaveLength(2); }); it('renders current branch text', () => { @@ -79,41 +73,38 @@ describe('IDE commit sidebar actions', () => { expect(findText()).not.toContain('Create a new branch and merge request'); }); - describe('currentBranchText', () => { - it('escapes current branch', () => { - const injectedSrc = ''; - createComponent({ currentBranchId: injectedSrc }); + it('escapes current branch name', () => { + const injectedSrc = ''; + const escapedSrc = '<img src="x" />'; + createComponent({ currentBranchId: injectedSrc }); - expect(vm.currentBranchText).not.toContain(injectedSrc); - }); + expect(wrapper.text()).not.toContain(injectedSrc); + expect(wrapper.text).not.toContain(escapedSrc); }); describe('updateSelectedCommitAction', () => { it('does not return anything if currentBranch does not exist', () => { createComponent({ currentBranchId: null }); - expect(vm.$store.dispatch).not.toHaveBeenCalled(); + expect(store.dispatch).not.toHaveBeenCalled(); }); it('is not called on mount if there is already a selected commitAction', () => { store.state.commitAction = '1'; createComponent({ currentBranchId: null }); - expect(vm.$store.dispatch).not.toHaveBeenCalled(); + expect(store.dispatch).not.toHaveBeenCalled(); }); it('calls again after staged changes', async () => { createComponent({ currentBranchId: null }); - vm.$store.state.currentBranchId = 'main'; - vm.$store.state.changedFiles.push({}); - vm.$store.state.stagedFiles.push({}); + store.state.currentBranchId = 'main'; + store.state.changedFiles.push({}); + store.state.stagedFiles.push({}); await nextTick(); - expect(vm.$store.dispatch).toHaveBeenCalledWith( - ACTION_UPDATE_COMMIT_ACTION, - expect.anything(), - ); + expect(store.dispatch).toHaveBeenCalledWith(ACTION_UPDATE_COMMIT_ACTION, expect.anything()); }); it.each` @@ -133,9 +124,7 @@ describe('IDE commit sidebar actions', () => { ({ input, expectedOption }) => { createComponent(input); - expect(vm.$store.dispatch.mock.calls).toEqual([ - [ACTION_UPDATE_COMMIT_ACTION, expectedOption], - ]); + expect(store.dispatch.mock.calls).toEqual([[ACTION_UPDATE_COMMIT_ACTION, expectedOption]]); }, ); }); diff --git a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js index dea920ecb5e..f71f6a5e5c7 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_item_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_item_spec.js @@ -1,14 +1,15 @@ +import { mount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import Vue, { nextTick } from 'vue'; import { trimText } from 'helpers/text_helper'; import waitForPromises from 'helpers/wait_for_promises'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import listItem from '~/ide/components/commit_sidebar/list_item.vue'; +import ListItem from '~/ide/components/commit_sidebar/list_item.vue'; import { createRouter } from '~/ide/ide_router'; import { createStore } from '~/ide/stores'; import { file } from '../../helpers'; describe('Multi-file editor commit sidebar list item', () => { - let vm; + let wrapper; let f; let findPathEl; let store; @@ -16,118 +17,120 @@ describe('Multi-file editor commit sidebar list item', () => { beforeEach(() => { store = createStore(); - router = createRouter(store); + jest.spyOn(store, 'dispatch'); - const Component = Vue.extend(listItem); + router = createRouter(store); f = file('test-file'); store.state.entries[f.path] = f; - vm = createComponentWithStore(Component, store, { - file: f, - activeFileKey: `staged-${f.key}`, - }).$mount(); + wrapper = mount(ListItem, { + store, + propsData: { + file: f, + activeFileKey: `staged-${f.key}`, + }, + }); - findPathEl = vm.$el.querySelector('.multi-file-commit-list-path'); + findPathEl = wrapper.find('.multi-file-commit-list-path'); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); - const findPathText = () => trimText(findPathEl.textContent); + const findPathText = () => trimText(findPathEl.text()); it('renders file path', () => { expect(findPathText()).toContain(f.path); }); it('correctly renders renamed entries', async () => { - Vue.set(vm.file, 'prevName', 'Old name'); - + Vue.set(f, 'prevName', 'Old name'); await nextTick(); + expect(findPathText()).toEqual(`Old name → ${f.name}`); }); it('correctly renders entry, the name of which did not change after rename (as within a folder)', async () => { - Vue.set(vm.file, 'prevName', f.name); - + Vue.set(f, 'prevName', f.name); await nextTick(); + expect(findPathText()).toEqual(f.name); }); it('opens a closed file in the editor when clicking the file path', async () => { - jest.spyOn(vm, 'openPendingTab'); jest.spyOn(router, 'push').mockImplementation(() => {}); - findPathEl.click(); - - await nextTick(); + await findPathEl.trigger('click'); - expect(vm.openPendingTab).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledWith('openPendingTab', expect.anything()); expect(router.push).toHaveBeenCalled(); }); it('calls updateViewer with diff when clicking file', async () => { - jest.spyOn(vm, 'openFileInEditor'); - jest.spyOn(vm, 'updateViewer'); jest.spyOn(router, 'push').mockImplementation(() => {}); - findPathEl.click(); - + await findPathEl.trigger('click'); await waitForPromises(); - expect(vm.updateViewer).toHaveBeenCalledWith('diff'); + expect(store.dispatch).toHaveBeenCalledWith('updateViewer', 'diff'); }); - describe('computed', () => { - describe('iconName', () => { - it('returns modified when not a tempFile', () => { - expect(vm.iconName).toBe('file-modified'); - }); + describe('icon name', () => { + const getIconName = () => wrapper.findComponent(GlIcon).props('name'); + + it('is modified when not a tempFile', () => { + expect(getIconName()).toBe('file-modified'); + }); - it('returns addition when not a tempFile', () => { - f.tempFile = true; + it('is addition when is a tempFile', async () => { + f.tempFile = true; + await nextTick(); - expect(vm.iconName).toBe('file-addition'); - }); + expect(getIconName()).toBe('file-addition'); + }); - it('returns deletion', () => { - f.deleted = true; + it('is deletion when is deleted', async () => { + f.deleted = true; + await nextTick(); - expect(vm.iconName).toBe('file-deletion'); - }); + expect(getIconName()).toBe('file-deletion'); }); + }); - describe('iconClass', () => { - it('returns modified when not a tempFile', () => { - expect(vm.iconClass).toContain('ide-file-modified'); - }); + describe('icon class', () => { + const getIconClass = () => wrapper.findComponent(GlIcon).classes(); - it('returns addition when not a tempFile', () => { - f.tempFile = true; + it('is modified when not a tempFile', () => { + expect(getIconClass()).toContain('ide-file-modified'); + }); - expect(vm.iconClass).toContain('ide-file-addition'); - }); + it('is addition when is a tempFile', async () => { + f.tempFile = true; + await nextTick(); - it('returns deletion', () => { - f.deleted = true; + expect(getIconClass()).toContain('ide-file-addition'); + }); - expect(vm.iconClass).toContain('ide-file-deletion'); - }); + it('returns deletion when is deleted', async () => { + f.deleted = true; + await nextTick(); + + expect(getIconClass()).toContain('ide-file-deletion'); }); }); describe('is active', () => { it('does not add active class when dont keys match', () => { - expect(vm.$el.querySelector('.is-active')).toBe(null); + expect(wrapper.find('.is-active').exists()).toBe(false); }); it('adds active class when keys match', async () => { - vm.keyPrefix = 'staged'; + await wrapper.setProps({ keyPrefix: 'staged' }); - await nextTick(); - expect(vm.$el.querySelector('.is-active')).not.toBe(null); + expect(wrapper.find('.is-active').exists()).toBe(true); }); }); }); diff --git a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js index ace266aec5e..c2ef29c1059 100644 --- a/spec/frontend/ide/components/commit_sidebar/message_field_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/message_field_spec.js @@ -1,135 +1,121 @@ -import Vue, { nextTick } from 'vue'; -import { setHTMLFixture, resetHTMLFixture } from 'helpers/fixtures'; -import createComponent from 'helpers/vue_mount_component_helper'; +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; import CommitMessageField from '~/ide/components/commit_sidebar/message_field.vue'; describe('IDE commit message field', () => { - const Component = Vue.extend(CommitMessageField); - let vm; + let wrapper; beforeEach(() => { - setHTMLFixture('
'); - - vm = createComponent( - Component, - { + wrapper = mount(CommitMessageField, { + propsData: { text: '', placeholder: 'testing', }, - '#app', - ); + attachTo: document.body, + }); }); afterEach(() => { - vm.$destroy(); - - resetHTMLFixture(); + wrapper.destroy(); }); + const findMessage = () => wrapper.find('textarea'); + const findHighlights = () => wrapper.findAll('.highlights span'); + const findMarks = () => wrapper.findAll('mark'); + it('adds is-focused class on focus', async () => { - vm.$el.querySelector('textarea').focus(); + await findMessage().trigger('focus'); - await nextTick(); - expect(vm.$el.querySelector('.is-focused')).not.toBeNull(); + expect(wrapper.find('.is-focused').exists()).toBe(true); }); it('removed is-focused class on blur', async () => { - vm.$el.querySelector('textarea').focus(); + await findMessage().trigger('focus'); - await nextTick(); - expect(vm.$el.querySelector('.is-focused')).not.toBeNull(); + expect(wrapper.find('.is-focused').exists()).toBe(true); - vm.$el.querySelector('textarea').blur(); + await findMessage().trigger('blur'); - await nextTick(); - expect(vm.$el.querySelector('.is-focused')).toBeNull(); + expect(wrapper.find('.is-focused').exists()).toBe(false); }); - it('emits input event on input', () => { - jest.spyOn(vm, '$emit').mockImplementation(); - - const textarea = vm.$el.querySelector('textarea'); - textarea.value = 'testing'; - - textarea.dispatchEvent(new Event('input')); + it('emits input event on input', async () => { + await findMessage().setValue('testing'); - expect(vm.$emit).toHaveBeenCalledWith('input', 'testing'); + expect(wrapper.emitted('input')[0]).toStrictEqual(['testing']); }); describe('highlights', () => { describe('subject line', () => { it('does not highlight less than 50 characters', async () => { - vm.text = 'text less than 50 chars'; + await wrapper.setProps({ text: 'text less than 50 chars' }); - await nextTick(); - expect(vm.$el.querySelector('.highlights span').textContent).toContain( - 'text less than 50 chars', - ); + expect(findHighlights()).toHaveLength(1); + expect(findHighlights().at(0).text()).toContain('text less than 50 chars'); - expect(vm.$el.querySelector('mark').style.display).toBe('none'); + expect(findMarks()).toHaveLength(1); + expect(findMarks().at(0).isVisible()).toBe(false); }); it('highlights characters over 50 length', async () => { - vm.text = - 'text less than 50 chars that should not highlighted. text more than 50 should be highlighted'; + await wrapper.setProps({ + text: + 'text less than 50 chars that should not highlighted. text more than 50 should be highlighted', + }); - await nextTick(); - expect(vm.$el.querySelector('.highlights span').textContent).toContain( + expect(findHighlights()).toHaveLength(1); + expect(findHighlights().at(0).text()).toContain( 'text less than 50 chars that should not highlighte', ); - expect(vm.$el.querySelector('mark').style.display).not.toBe('none'); - expect(vm.$el.querySelector('mark').textContent).toBe( - 'd. text more than 50 should be highlighted', - ); + expect(findMarks()).toHaveLength(1); + expect(findMarks().at(0).isVisible()).toBe(true); + expect(findMarks().at(0).text()).toBe('d. text more than 50 should be highlighted'); }); }); describe('body text', () => { it('does not highlight body text less tan 72 characters', async () => { - vm.text = 'subject line\nbody content'; + await wrapper.setProps({ text: 'subject line\nbody content' }); - await nextTick(); - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark')[1].style.display).toBe('none'); + expect(findHighlights()).toHaveLength(2); + expect(findMarks().at(1).isVisible()).toBe(false); }); it('highlights body text more than 72 characters', async () => { - vm.text = - 'subject line\nbody content that will be highlighted when it is more than 72 characters in length'; - - await nextTick(); - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark')[1].style.display).not.toBe('none'); - expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length'); + await wrapper.setProps({ + text: + 'subject line\nbody content that will be highlighted when it is more than 72 characters in length', + }); + + expect(findHighlights()).toHaveLength(2); + expect(findMarks().at(1).isVisible()).toBe(true); + expect(findMarks().at(1).text()).toBe('in length'); }); it('highlights body text & subject line', async () => { - vm.text = - 'text less than 50 chars that should not highlighted\nbody content that will be highlighted when it is more than 72 characters in length'; + await wrapper.setProps({ + text: + 'text less than 50 chars that should not highlighted\nbody content that will be highlighted when it is more than 72 characters in length', + }); - await nextTick(); - expect(vm.$el.querySelectorAll('.highlights span').length).toBe(2); - expect(vm.$el.querySelectorAll('mark').length).toBe(2); + expect(findHighlights()).toHaveLength(2); + expect(findMarks()).toHaveLength(2); - expect(vm.$el.querySelectorAll('mark')[0].textContent).toContain('d'); - expect(vm.$el.querySelectorAll('mark')[1].textContent).toBe(' in length'); + expect(findMarks().at(0).text()).toContain('d'); + expect(findMarks().at(1).text()).toBe('in length'); }); }); }); describe('scrolling textarea', () => { it('updates transform of highlights', async () => { - vm.text = 'subject line\n\n\n\n\n\n\n\n\n\n\nbody content'; + await wrapper.setProps({ text: 'subject line\n\n\n\n\n\n\n\n\n\n\nbody content' }); + findMessage().element.scrollTo(0, 50); await nextTick(); - vm.$el.querySelector('textarea').scrollTo(0, 50); - vm.handleScroll(); - - await nextTick(); - expect(vm.scrollTop).toBe(50); - expect(vm.$el.querySelector('.highlights').style.transform).toBe('translate3d(0, -50px, 0)'); + expect(wrapper.find('.highlights').element.style.transform).toBe('translate3d(0, -50px, 0)'); }); }); }); diff --git a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js index ee6ed694285..a3fa03a4aa5 100644 --- a/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/radio_group_spec.js @@ -1,123 +1,116 @@ -import Vue, { nextTick } from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { GlFormRadioGroup } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; import RadioGroup from '~/ide/components/commit_sidebar/radio_group.vue'; import { createStore } from '~/ide/stores'; +import { createMockDirective, getBinding } from 'helpers/vue_mock_directive'; describe('IDE commit sidebar radio group', () => { - let vm; + let wrapper; let store; - beforeEach(async () => { + const createComponent = (config = {}) => { store = createStore(); - const Component = Vue.extend(RadioGroup); - store.state.commit.commitAction = '2'; + store.state.commit.newBranchName = 'test-123'; - vm = createComponentWithStore(Component, store, { - value: '1', - label: 'test', - checked: true, + wrapper = mount(RadioGroup, { + store, + propsData: config.props, + slots: config.slots, + directives: { + GlTooltip: createMockDirective(), + }, }); - - vm.$mount(); - - await nextTick(); - }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); - it('uses label if present', () => { - expect(vm.$el.textContent).toContain('test'); - }); + describe('without input', () => { + const props = { + value: '1', + label: 'test', + checked: true, + }; - it('uses slot if label is not present', async () => { - vm.$destroy(); + it('uses label if present', () => { + createComponent({ props }); - vm = new Vue({ - components: { - RadioGroup, - }, - store, - render: (createElement) => - createElement('radio-group', { props: { value: '1' } }, 'Testing slot'), + expect(wrapper.text()).toContain('test'); }); - vm.$mount(); + it('uses slot if label is not present', () => { + createComponent({ props: { value: '1', checked: true }, slots: { default: 'Testing slot' } }); - await nextTick(); - expect(vm.$el.textContent).toContain('Testing slot'); - }); + expect(wrapper.text()).toContain('Testing slot'); + }); - it('updates store when changing radio button', async () => { - vm.$el.querySelector('input').dispatchEvent(new Event('change')); + it('updates store when changing radio button', async () => { + createComponent({ props }); - await nextTick(); - expect(store.state.commit.commitAction).toBe('1'); + await wrapper.find('input').trigger('change'); + + expect(store.state.commit.commitAction).toBe('1'); + }); }); describe('with input', () => { - beforeEach(async () => { - vm.$destroy(); - - const Component = Vue.extend(RadioGroup); - - store.state.commit.commitAction = '1'; - store.state.commit.newBranchName = 'test-123'; - - vm = createComponentWithStore(Component, store, { - value: '1', - label: 'test', - checked: true, - showInput: true, - }); - - vm.$mount(); - - await nextTick(); - }); + const props = { + value: '2', + label: 'test', + checked: true, + showInput: true, + }; it('renders input box when commitAction matches value', () => { - expect(vm.$el.querySelector('.form-control')).not.toBeNull(); + createComponent({ props: { ...props, value: '2' } }); + + expect(wrapper.find('.form-control').exists()).toBe(true); }); - it('hides input when commitAction doesnt match value', async () => { - store.state.commit.commitAction = '2'; + it('hides input when commitAction doesnt match value', () => { + createComponent({ props: { ...props, value: '1' } }); - await nextTick(); - expect(vm.$el.querySelector('.form-control')).toBeNull(); + expect(wrapper.find('.form-control').exists()).toBe(false); }); it('updates branch name in store on input', async () => { - const input = vm.$el.querySelector('.form-control'); - input.value = 'testing-123'; - input.dispatchEvent(new Event('input')); + createComponent({ props }); + + await wrapper.find('.form-control').setValue('testing-123'); - await nextTick(); expect(store.state.commit.newBranchName).toBe('testing-123'); }); it('renders newBranchName if present', () => { - const input = vm.$el.querySelector('.form-control'); + createComponent({ props }); - expect(input.value).toBe('test-123'); + const input = wrapper.find('.form-control'); + + expect(input.element.value).toBe('test-123'); }); }); describe('tooltipTitle', () => { it('returns title when disabled', () => { - vm.title = 'test title'; - vm.disabled = true; + createComponent({ + props: { value: '1', label: 'test', disabled: true, title: 'test title' }, + }); - expect(vm.tooltipTitle).toBe('test title'); + const tooltip = getBinding(wrapper.findComponent(GlFormRadioGroup).element, 'gl-tooltip'); + expect(tooltip.value).toBe('test title'); }); it('returns blank when not disabled', () => { - vm.title = 'test title'; + createComponent({ + props: { value: '1', label: 'test', title: 'test title' }, + }); + + const tooltip = getBinding(wrapper.findComponent(GlFormRadioGroup).element, 'gl-tooltip'); - expect(vm.tooltipTitle).not.toBe('test title'); + expect(tooltip.value).toBe(''); }); }); }); diff --git a/spec/frontend/ide/components/file_row_extra_spec.js b/spec/frontend/ide/components/file_row_extra_spec.js index 5a7a1fe7db0..281c549a1b4 100644 --- a/spec/frontend/ide/components/file_row_extra_spec.js +++ b/spec/frontend/ide/components/file_row_extra_spec.js @@ -1,146 +1,146 @@ -import Vue, { nextTick } from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { mount } from '@vue/test-utils'; import FileRowExtra from '~/ide/components/file_row_extra.vue'; -import { createStore } from '~/ide/stores'; +import { createStoreOptions } from '~/ide/stores'; import { file } from '../helpers'; describe('IDE extra file row component', () => { - let Component; - let vm; + let wrapper; + let store; let unstagedFilesCount = 0; let stagedFilesCount = 0; let changesCount = 0; - beforeAll(() => { - Component = Vue.extend(FileRowExtra); - }); + const createComponent = (fileProps) => { + const storeConfig = createStoreOptions(); - beforeEach(() => { - vm = createComponentWithStore(Component, createStore(), { - file: { - ...file('test'), + store = new Vuex.Store({ + ...storeConfig, + getters: { + getUnstagedFilesCountForPath: () => () => unstagedFilesCount, + getStagedFilesCountForPath: () => () => stagedFilesCount, + getChangesInFolder: () => () => changesCount, }, - dropdownOpen: false, }); - jest.spyOn(vm, 'getUnstagedFilesCountForPath', 'get').mockReturnValue(() => unstagedFilesCount); - jest.spyOn(vm, 'getStagedFilesCountForPath', 'get').mockReturnValue(() => stagedFilesCount); - jest.spyOn(vm, 'getChangesInFolder', 'get').mockReturnValue(() => changesCount); - - vm.$mount(); - }); + wrapper = mount(FileRowExtra, { + store, + propsData: { + file: { + ...file('test'), + type: 'tree', + ...fileProps, + }, + dropdownOpen: false, + }, + }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); stagedFilesCount = 0; unstagedFilesCount = 0; changesCount = 0; }); - describe('folderChangesTooltip', () => { - it('returns undefined when changes count is 0', () => { - changesCount = 0; - - expect(vm.folderChangesTooltip).toBe(undefined); - }); - + describe('folder changes tooltip', () => { [ { input: 1, output: '1 changed file' }, { input: 2, output: '2 changed files' }, ].forEach(({ input, output }) => { - it('returns changed files count if changes count is not 0', () => { + it('shows changed files count if changes count is not 0', () => { changesCount = input; + createComponent(); - expect(vm.folderChangesTooltip).toBe(output); + expect(wrapper.find('.ide-file-modified').attributes('title')).toBe(output); }); }); }); describe('show tree changes count', () => { + const findTreeChangesCount = () => wrapper.find('.ide-tree-changes'); + it('does not show for blobs', () => { - vm.file.type = 'blob'; + createComponent({ type: 'blob' }); - expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + expect(findTreeChangesCount().exists()).toBe(false); }); it('does not show when changes count is 0', () => { - vm.file.type = 'tree'; + createComponent({ type: 'tree' }); - expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + expect(findTreeChangesCount().exists()).toBe(false); }); - it('does not show when tree is open', async () => { - vm.file.type = 'tree'; - vm.file.opened = true; + it('does not show when tree is open', () => { changesCount = 1; + createComponent({ type: 'tree', opened: true }); - await nextTick(); - expect(vm.$el.querySelector('.ide-tree-changes')).toBe(null); + expect(findTreeChangesCount().exists()).toBe(false); }); - it('shows for trees with changes', async () => { - vm.file.type = 'tree'; - vm.file.opened = false; + it('shows for trees with changes', () => { changesCount = 1; + createComponent({ type: 'tree', opened: false }); - await nextTick(); - expect(vm.$el.querySelector('.ide-tree-changes')).not.toBe(null); + expect(findTreeChangesCount().exists()).toBe(true); }); }); describe('changes file icon', () => { + const findChangedFileIcon = () => wrapper.find('.file-changed-icon'); + it('hides when file is not changed', () => { - expect(vm.$el.querySelector('.file-changed-icon')).toBe(null); + createComponent(); + + expect(findChangedFileIcon().exists()).toBe(false); }); - it('shows when file is changed', async () => { - vm.file.changed = true; + it('shows when file is changed', () => { + createComponent({ type: 'blob', changed: true }); - await nextTick(); - expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null); + expect(findChangedFileIcon().exists()).toBe(true); }); - it('shows when file is staged', async () => { - vm.file.staged = true; + it('shows when file is staged', () => { + createComponent({ type: 'blob', staged: true }); - await nextTick(); - expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null); + expect(findChangedFileIcon().exists()).toBe(true); }); - it('shows when file is a tempFile', async () => { - vm.file.tempFile = true; + it('shows when file is a tempFile', () => { + createComponent({ type: 'blob', tempFile: true }); - await nextTick(); - expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null); + expect(findChangedFileIcon().exists()).toBe(true); }); - it('shows when file is renamed', async () => { - vm.file.prevPath = 'original-file'; + it('shows when file is renamed', () => { + createComponent({ type: 'blob', prevPath: 'original-file' }); - await nextTick(); - expect(vm.$el.querySelector('.file-changed-icon')).not.toBe(null); + expect(findChangedFileIcon().exists()).toBe(true); }); - it('hides when file is renamed', async () => { - vm.file.prevPath = 'original-file'; - vm.file.type = 'tree'; + it('hides when tree is renamed', () => { + createComponent({ type: 'tree', prevPath: 'original-path' }); - await nextTick(); - expect(vm.$el.querySelector('.file-changed-icon')).toBe(null); + expect(findChangedFileIcon().exists()).toBe(false); }); }); describe('merge request icon', () => { + const findMergeRequestIcon = () => wrapper.find('[data-testid="git-merge-icon"]'); + it('hides when not a merge request change', () => { - expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).toBe(null); + createComponent(); + + expect(findMergeRequestIcon().exists()).toBe(false); }); - it('shows when a merge request change', async () => { - vm.file.mrChange = true; + it('shows when a merge request change', () => { + createComponent({ mrChange: true }); - await nextTick(); - expect(vm.$el.querySelector('[data-testid="git-merge-icon"]')).not.toBe(null); + expect(findMergeRequestIcon().exists()).toBe(true); }); }); }); diff --git a/spec/frontend/ide/components/file_templates/bar_spec.js b/spec/frontend/ide/components/file_templates/bar_spec.js index aaf9c17ccbf..60f37260393 100644 --- a/spec/frontend/ide/components/file_templates/bar_spec.js +++ b/spec/frontend/ide/components/file_templates/bar_spec.js @@ -1,19 +1,16 @@ -import Vue, { nextTick } from 'vue'; -import { mountComponentWithStore } from 'helpers/vue_mount_component_helper'; +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; import Bar from '~/ide/components/file_templates/bar.vue'; import { createStore } from '~/ide/stores'; import { file } from '../../helpers'; describe('IDE file templates bar component', () => { - let Component; - let vm; - - beforeAll(() => { - Component = Vue.extend(Bar); - }); + let wrapper; + let store; beforeEach(() => { - const store = createStore(); + store = createStore(); + jest.spyOn(store, 'dispatch').mockImplementation(); store.state.openFiles.push({ ...file('file'), @@ -21,24 +18,22 @@ describe('IDE file templates bar component', () => { active: true, }); - vm = mountComponentWithStore(Component, { store }); + wrapper = mount(Bar, { store }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); describe('template type dropdown', () => { it('renders dropdown component', () => { - expect(vm.$el.querySelector('.dropdown').textContent).toContain('Choose a type'); + expect(wrapper.find('.dropdown').text()).toContain('Choose a type'); }); - it('calls setSelectedTemplateType when clicking item', () => { - jest.spyOn(vm, 'setSelectedTemplateType').mockImplementation(); - - vm.$el.querySelector('.dropdown-menu button').click(); + it('calls setSelectedTemplateType when clicking item', async () => { + await wrapper.find('.dropdown-menu button').trigger('click'); - expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({ + expect(store.dispatch).toHaveBeenCalledWith('fileTemplates/setSelectedTemplateType', { name: '.gitlab-ci.yml', key: 'gitlab_ci_ymls', }); @@ -46,60 +41,52 @@ describe('IDE file templates bar component', () => { }); describe('template dropdown', () => { - beforeEach(async () => { - vm.$store.state.fileTemplates.templates = [ + beforeEach(() => { + store.state.fileTemplates.templates = [ { name: 'test', }, ]; - vm.$store.state.fileTemplates.selectedTemplateType = { + store.state.fileTemplates.selectedTemplateType = { name: '.gitlab-ci.yml', key: 'gitlab_ci_ymls', }; - - await nextTick(); }); it('renders dropdown component', () => { - expect(vm.$el.querySelectorAll('.dropdown')[1].textContent).toContain('Choose a template'); + expect(wrapper.findAll('.dropdown').at(1).text()).toContain('Choose a template'); }); - it('calls fetchTemplate on dropdown open', () => { - jest.spyOn(vm, 'fetchTemplate').mockImplementation(); - - vm.$el.querySelectorAll('.dropdown-menu')[1].querySelector('button').click(); + it('calls fetchTemplate on dropdown open', async () => { + await wrapper.findAll('.dropdown-menu').at(1).find('button').trigger('click'); - expect(vm.fetchTemplate).toHaveBeenCalledWith({ + expect(store.dispatch).toHaveBeenCalledWith('fileTemplates/fetchTemplate', { name: 'test', }); }); }); + const findUndoButton = () => wrapper.find('.btn-default-secondary'); it('shows undo button if updateSuccess is true', async () => { - vm.$store.state.fileTemplates.updateSuccess = true; - + store.state.fileTemplates.updateSuccess = true; await nextTick(); - expect(vm.$el.querySelector('.btn-default').style.display).not.toBe('none'); - }); - it('calls undoFileTemplate when clicking undo button', () => { - jest.spyOn(vm, 'undoFileTemplate').mockImplementation(); + expect(findUndoButton().isVisible()).toBe(true); + }); - vm.$el.querySelector('.btn-default-secondary').click(); + it('calls undoFileTemplate when clicking undo button', async () => { + await findUndoButton().trigger('click'); - expect(vm.undoFileTemplate).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledWith('fileTemplates/undoFileTemplate', undefined); }); it('calls setSelectedTemplateType if activeFile name matches a template', async () => { const fileName = '.gitlab-ci.yml'; - - jest.spyOn(vm, 'setSelectedTemplateType').mockImplementation(() => {}); - vm.$store.state.openFiles[0].name = fileName; - - vm.setInitialType(); + store.state.openFiles = [{ ...file(fileName), opened: true, active: true }]; await nextTick(); - expect(vm.setSelectedTemplateType).toHaveBeenCalledWith({ + + expect(store.dispatch).toHaveBeenCalledWith('fileTemplates/setSelectedTemplateType', { name: fileName, key: 'gitlab_ci_ymls', }); diff --git a/spec/frontend/ide/components/jobs/detail/description_spec.js b/spec/frontend/ide/components/jobs/detail/description_spec.js index 128ccff6568..629c4424314 100644 --- a/spec/frontend/ide/components/jobs/detail/description_spec.js +++ b/spec/frontend/ide/components/jobs/detail/description_spec.js @@ -1,44 +1,43 @@ -import Vue from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; +import { GlIcon } from '@gitlab/ui'; import Description from '~/ide/components/jobs/detail/description.vue'; import { jobs } from '../../../mock_data'; describe('IDE job description', () => { - const Component = Vue.extend(Description); - let vm; + let wrapper; beforeEach(() => { - vm = mountComponent(Component, { - job: jobs[0], + wrapper = mount(Description, { + propsData: { + job: jobs[0], + }, }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders job details', () => { - expect(vm.$el.textContent).toContain('#1'); - expect(vm.$el.textContent).toContain('test'); + expect(wrapper.text()).toContain('#1'); + expect(wrapper.text()).toContain('test'); }); it('renders CI icon', () => { - expect( - vm.$el.querySelector('.ci-status-icon [data-testid="status_success_borderless-icon"]'), - ).not.toBe(null); + expect(wrapper.find('.ci-status-icon').findComponent(GlIcon).exists()).toBe(true); }); it('renders a borderless CI icon', () => { - expect( - vm.$el.querySelector('.borderless [data-testid="status_success_borderless-icon"]'), - ).not.toBe(null); + expect(wrapper.find('.borderless').findComponent(GlIcon).exists()).toBe(true); }); it('renders bridge job details without the job link', () => { - vm = mountComponent(Component, { - job: { ...jobs[0], path: undefined }, + wrapper = mount(Description, { + propsData: { + job: { ...jobs[0], path: undefined }, + }, }); - expect(vm.$el.querySelector('[data-testid="description-detail-link"]')).toBe(null); + expect(wrapper.find('[data-testid="description-detail-link"]').exists()).toBe(false); }); }); diff --git a/spec/frontend/ide/components/jobs/detail_spec.js b/spec/frontend/ide/components/jobs/detail_spec.js index 9122471d421..bf2be3aa595 100644 --- a/spec/frontend/ide/components/jobs/detail_spec.js +++ b/spec/frontend/ide/components/jobs/detail_spec.js @@ -1,15 +1,17 @@ -import Vue, { nextTick } from 'vue'; +import { nextTick } from 'vue'; +import { mount } from '@vue/test-utils'; + import { TEST_HOST } from 'helpers/test_constants'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; import JobDetail from '~/ide/components/jobs/detail.vue'; import { createStore } from '~/ide/stores'; import { jobs } from '../../mock_data'; describe('IDE jobs detail view', () => { - let vm; + let wrapper; + let store; const createComponent = () => { - const store = createStore(); + store = createStore(); store.state.pipelines.detailJob = { ...jobs[0], @@ -18,163 +20,129 @@ describe('IDE jobs detail view', () => { rawPath: `${TEST_HOST}/raw`, }; - return createComponentWithStore(Vue.extend(JobDetail), store); + jest.spyOn(store, 'dispatch'); + store.dispatch.mockResolvedValue(); + + wrapper = mount(JobDetail, { store }); }; - beforeEach(() => { - vm = createComponent(); + const findBuildJobLog = () => wrapper.find('pre'); + const findScrollToBottomButton = () => wrapper.find('button[aria-label="Scroll to bottom"]'); + const findScrollToTopButton = () => wrapper.find('button[aria-label="Scroll to top"]'); - jest.spyOn(vm, 'fetchJobLogs').mockResolvedValue(); + beforeEach(() => { + createComponent(); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); describe('mounted', () => { - beforeEach(() => { - vm = vm.$mount(); - }); + const findJobOutput = () => wrapper.find('.bash'); + const findBuildLoaderAnimation = () => wrapper.find('.build-loader-animation'); it('calls fetchJobLogs', () => { - expect(vm.fetchJobLogs).toHaveBeenCalled(); + expect(store.dispatch).toHaveBeenCalledWith('pipelines/fetchJobLogs', undefined); }); it('scrolls to bottom', () => { - expect(vm.$refs.buildJobLog.scrollTo).toHaveBeenCalled(); + expect(findBuildJobLog().element.scrollTo).toHaveBeenCalled(); }); it('renders job output', () => { - expect(vm.$el.querySelector('.bash').textContent).toContain('testing'); + expect(findJobOutput().text()).toContain('testing'); }); it('renders empty message output', async () => { - vm.$store.state.pipelines.detailJob.output = ''; - + store.state.pipelines.detailJob.output = ''; await nextTick(); - expect(vm.$el.querySelector('.bash').textContent).toContain('No messages were logged'); + + expect(findJobOutput().text()).toContain('No messages were logged'); }); it('renders loading icon', () => { - expect(vm.$el.querySelector('.build-loader-animation')).not.toBe(null); - expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe(''); + expect(findBuildLoaderAnimation().exists()).toBe(true); + expect(findBuildLoaderAnimation().isVisible()).toBe(true); }); it('hides output when loading', () => { - expect(vm.$el.querySelector('.bash')).not.toBe(null); - expect(vm.$el.querySelector('.bash').style.display).toBe('none'); + expect(findJobOutput().exists()).toBe(true); + expect(findJobOutput().isVisible()).toBe(false); }); it('hide loading icon when isLoading is false', async () => { - vm.$store.state.pipelines.detailJob.isLoading = false; - + store.state.pipelines.detailJob.isLoading = false; await nextTick(); - expect(vm.$el.querySelector('.build-loader-animation').style.display).toBe('none'); - }); - it('resets detailJob when clicking header button', () => { - jest.spyOn(vm, 'setDetailJob').mockImplementation(); + expect(findBuildLoaderAnimation().isVisible()).toBe(false); + }); - vm.$el.querySelector('.btn').click(); + it('resets detailJob when clicking header button', async () => { + await wrapper.find('.btn').trigger('click'); - expect(vm.setDetailJob).toHaveBeenCalledWith(null); + expect(store.dispatch).toHaveBeenCalledWith('pipelines/setDetailJob', null); }); it('renders raw path link', () => { - expect(vm.$el.querySelector('.controllers-buttons').getAttribute('href')).toBe( - `${TEST_HOST}/raw`, - ); + expect(wrapper.find('.controllers-buttons').attributes('href')).toBe(`${TEST_HOST}/raw`); }); }); describe('scroll buttons', () => { beforeEach(() => { - vm = createComponent(); - jest.spyOn(vm, 'fetchJobLogs').mockResolvedValue(); - }); - - afterEach(() => { - vm.$destroy(); + createComponent(); }); it.each` - fnName | btnName | scrollPos - ${'scrollDown'} | ${'down'} | ${0} - ${'scrollUp'} | ${'up'} | ${1} - `('triggers $fnName when clicking $btnName button', async ({ fnName, scrollPos }) => { - jest.spyOn(vm, fnName).mockImplementation(); - - vm = vm.$mount(); + fnName | btnName | scrollPos | targetScrollPos + ${'scroll down'} | ${'down'} | ${0} | ${200} + ${'scroll up'} | ${'up'} | ${200} | ${0} + `('triggers $fnName when clicking $btnName button', async ({ scrollPos, targetScrollPos }) => { + jest.spyOn(findBuildJobLog().element, 'offsetHeight', 'get').mockReturnValue(0); + jest.spyOn(findBuildJobLog().element, 'scrollHeight', 'get').mockReturnValue(200); + jest.spyOn(findBuildJobLog().element, 'scrollTop', 'get').mockReturnValue(scrollPos); + findBuildJobLog().element.scrollTo.mockReset(); - vm.scrollPos = scrollPos; - - await nextTick(); - vm.$el.querySelector('.btn-scroll:not([disabled])').click(); - expect(vm[fnName]).toHaveBeenCalled(); - }); - }); - - describe('scrollDown', () => { - beforeEach(() => { - vm = vm.$mount(); - - jest.spyOn(vm.$refs.buildJobLog, 'scrollTo').mockImplementation(); - }); - - it('scrolls build trace to bottom', () => { - jest.spyOn(vm.$refs.buildJobLog, 'scrollHeight', 'get').mockReturnValue(1000); - - vm.scrollDown(); - - expect(vm.$refs.buildJobLog.scrollTo).toHaveBeenCalledWith(0, 1000); - }); - }); - - describe('scrollUp', () => { - beforeEach(() => { - vm = vm.$mount(); - - jest.spyOn(vm.$refs.buildJobLog, 'scrollTo').mockImplementation(); - }); + await findBuildJobLog().trigger('scroll'); // trigger button updates - it('scrolls build trace to top', () => { - vm.scrollUp(); + await wrapper.find('.controllers button:not(:disabled)').trigger('click'); - expect(vm.$refs.buildJobLog.scrollTo).toHaveBeenCalledWith(0, 0); + expect(findBuildJobLog().element.scrollTo).toHaveBeenCalledWith(0, targetScrollPos); }); }); - describe('scrollBuildLog', () => { + describe('scrolling build log', () => { beforeEach(() => { - vm = vm.$mount(); - jest.spyOn(vm.$refs.buildJobLog, 'scrollTo').mockImplementation(); - jest.spyOn(vm.$refs.buildJobLog, 'offsetHeight', 'get').mockReturnValue(100); - jest.spyOn(vm.$refs.buildJobLog, 'scrollHeight', 'get').mockReturnValue(200); + jest.spyOn(findBuildJobLog().element, 'offsetHeight', 'get').mockReturnValue(100); + jest.spyOn(findBuildJobLog().element, 'scrollHeight', 'get').mockReturnValue(200); }); - it('sets scrollPos to bottom when at the bottom', () => { - jest.spyOn(vm.$refs.buildJobLog, 'scrollTop', 'get').mockReturnValue(100); + it('keeps scroll at bottom when already at the bottom', async () => { + jest.spyOn(findBuildJobLog().element, 'scrollTop', 'get').mockReturnValue(100); - vm.scrollBuildLog(); + await findBuildJobLog().trigger('scroll'); - expect(vm.scrollPos).toBe(1); + expect(findScrollToBottomButton().attributes('disabled')).toBe('disabled'); + expect(findScrollToTopButton().attributes('disabled')).not.toBe('disabled'); }); - it('sets scrollPos to top when at the top', () => { - jest.spyOn(vm.$refs.buildJobLog, 'scrollTop', 'get').mockReturnValue(0); - vm.scrollPos = 1; + it('keeps scroll at top when already at top', async () => { + jest.spyOn(findBuildJobLog().element, 'scrollTop', 'get').mockReturnValue(0); - vm.scrollBuildLog(); + await findBuildJobLog().trigger('scroll'); - expect(vm.scrollPos).toBe(0); + expect(findScrollToBottomButton().attributes('disabled')).not.toBe('disabled'); + expect(findScrollToTopButton().attributes('disabled')).toBe('disabled'); }); - it('resets scrollPos when not at top or bottom', () => { - jest.spyOn(vm.$refs.buildJobLog, 'scrollTop', 'get').mockReturnValue(10); + it('resets scroll when not at top or bottom', async () => { + jest.spyOn(findBuildJobLog().element, 'scrollTop', 'get').mockReturnValue(10); - vm.scrollBuildLog(); + await findBuildJobLog().trigger('scroll'); - expect(vm.scrollPos).toBe(''); + expect(findScrollToBottomButton().attributes('disabled')).not.toBe('disabled'); + expect(findScrollToTopButton().attributes('disabled')).not.toBe('disabled'); }); }); }); diff --git a/spec/frontend/ide/components/jobs/item_spec.js b/spec/frontend/ide/components/jobs/item_spec.js index c76760a5522..32e27333e42 100644 --- a/spec/frontend/ide/components/jobs/item_spec.js +++ b/spec/frontend/ide/components/jobs/item_spec.js @@ -1,36 +1,38 @@ -import Vue, { nextTick } from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; +import { GlButton } from '@gitlab/ui'; + import JobItem from '~/ide/components/jobs/item.vue'; import { jobs } from '../../mock_data'; describe('IDE jobs item', () => { - const Component = Vue.extend(JobItem); const job = jobs[0]; - let vm; + let wrapper; beforeEach(() => { - vm = mountComponent(Component, { - job, - }); + wrapper = mount(JobItem, { propsData: { job } }); }); afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders job details', () => { - expect(vm.$el.textContent).toContain(job.name); - expect(vm.$el.textContent).toContain(`#${job.id}`); + expect(wrapper.text()).toContain(job.name); + expect(wrapper.text()).toContain(`#${job.id}`); }); it('renders CI icon', () => { - expect(vm.$el.querySelector('[data-testid="status_success_borderless-icon"]')).not.toBe(null); + expect(wrapper.find('[data-testid="status_success_borderless-icon"]').exists()).toBe(true); }); it('does not render view logs button if not started', async () => { - vm.job.started = false; + await wrapper.setProps({ + job: { + ...jobs[0], + started: false, + }, + }); - await nextTick(); - expect(vm.$el.querySelector('.btn')).toBe(null); + expect(wrapper.findComponent(GlButton).exists()).toBe(false); }); }); diff --git a/spec/frontend/ide/components/new_dropdown/button_spec.js b/spec/frontend/ide/components/new_dropdown/button_spec.js index 298d7b810e1..a9cfdfd20c1 100644 --- a/spec/frontend/ide/components/new_dropdown/button_spec.js +++ b/spec/frontend/ide/components/new_dropdown/button_spec.js @@ -1,59 +1,60 @@ -import Vue, { nextTick } from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; 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', + let wrapper; + + const createComponent = (props = {}) => { + wrapper = mount(Button, { + propsData: { + label: 'Testing', + icon: 'doc-new', + ...props, + }, }); - - jest.spyOn(vm, '$emit').mockImplementation(() => {}); - }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders button with label', () => { - expect(vm.$el.textContent).toContain('Testing'); + createComponent(); + + expect(wrapper.text()).toContain('Testing'); }); it('renders icon', () => { - expect(vm.$el.querySelector('[data-testid="doc-new-icon"]')).not.toBe(null); + createComponent(); + + expect(wrapper.find('[data-testid="doc-new-icon"]').exists()).toBe(true); }); - it('emits click event', () => { - vm.$el.click(); + it('emits click event', async () => { + createComponent(); - expect(vm.$emit).toHaveBeenCalledWith('click'); + await wrapper.trigger('click'); + + expect(wrapper.emitted('click')).toHaveLength(1); }); - it('hides label if showLabel is false', async () => { - vm.showLabel = false; + it('hides label if showLabel is false', () => { + createComponent({ showLabel: false }); - await nextTick(); - expect(vm.$el.textContent).not.toContain('Testing'); + expect(wrapper.text()).not.toContain('Testing'); }); - describe('tooltipTitle', () => { + describe('tooltip title', () => { it('returns empty string when showLabel is true', () => { - expect(vm.tooltipTitle).toBe(''); + createComponent({ showLabel: true }); + + expect(wrapper.attributes('title')).toBe(''); }); - it('returns label', async () => { - vm.showLabel = false; + it('returns label', () => { + createComponent({ showLabel: false }); - await nextTick(); - expect(vm.tooltipTitle).toBe('Testing'); + expect(wrapper.attributes('title')).toBe('Testing'); }); }); }); diff --git a/spec/frontend/ide/components/new_dropdown/upload_spec.js b/spec/frontend/ide/components/new_dropdown/upload_spec.js index 3eafe9e7ccb..fc643589d51 100644 --- a/spec/frontend/ide/components/new_dropdown/upload_spec.js +++ b/spec/frontend/ide/components/new_dropdown/upload_spec.js @@ -1,39 +1,34 @@ -import Vue from 'vue'; -import createComponent from 'helpers/vue_mount_component_helper'; -import upload from '~/ide/components/new_dropdown/upload.vue'; +import { mount } from '@vue/test-utils'; +import Upload from '~/ide/components/new_dropdown/upload.vue'; describe('new dropdown upload', () => { - let vm; + let wrapper; beforeEach(() => { - const Component = Vue.extend(upload); - - vm = createComponent(Component, { - path: '', + wrapper = mount(Upload, { + propsData: { + path: '', + }, }); - - vm.entryName = 'testing'; - - jest.spyOn(vm, '$emit'); }); afterEach(() => { - vm.$destroy(); + wrapper.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); + jest.spyOn(wrapper.vm, 'readFile').mockImplementation(() => {}); + jest.spyOn(wrapper.vm.$refs.fileUpload, 'files', 'get').mockReturnValue(files); - vm.openFile(); + wrapper.vm.openFile(); - expect(vm.readFile.mock.calls.length).toBe(3); + expect(wrapper.vm.readFile.mock.calls.length).toBe(3); files.forEach((file, i) => { - expect(vm.readFile.mock.calls[i]).toEqual([file]); + expect(wrapper.vm.readFile.mock.calls[i]).toEqual([file]); }); }); }); @@ -48,7 +43,7 @@ describe('new dropdown upload', () => { type: 'images/png', }; - vm.readFile(file); + wrapper.vm.readFile(file); expect(FileReader.prototype.readAsDataURL).toHaveBeenCalledWith(file); }); @@ -71,35 +66,39 @@ describe('new dropdown upload', () => { it('calls readAsText and creates file in plain text (without encoding) if the file content is plain text', async () => { const waitForCreate = new Promise((resolve) => { - vm.$on('create', resolve); + wrapper.vm.$on('create', resolve); }); - vm.createFile(textTarget, textFile); + wrapper.vm.createFile(textTarget, textFile); expect(FileReader.prototype.readAsText).toHaveBeenCalledWith(textFile); await waitForCreate; - expect(vm.$emit).toHaveBeenCalledWith('create', { - name: textFile.name, - type: 'blob', - content: 'plain text', - rawPath: '', - mimeType: 'test/mime-text', - }); + expect(wrapper.emitted('create')[0]).toStrictEqual([ + { + name: textFile.name, + type: 'blob', + content: 'plain text', + rawPath: '', + mimeType: 'test/mime-text', + }, + ]); }); it('creates a blob URL for the content if binary', () => { - vm.createFile(binaryTarget, binaryFile); + wrapper.vm.createFile(binaryTarget, binaryFile); expect(FileReader.prototype.readAsText).not.toHaveBeenCalled(); - expect(vm.$emit).toHaveBeenCalledWith('create', { - name: binaryFile.name, - type: 'blob', - content: 'ðððð', - rawPath: 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b', - mimeType: 'test/mime-binary', - }); + expect(wrapper.emitted('create')[0]).toStrictEqual([ + { + name: binaryFile.name, + type: 'blob', + content: 'ðððð', + rawPath: 'blob:https://gitlab.com/048c7ac1-98de-4a37-ab1b-0206d0ea7e1b', + mimeType: 'test/mime-binary', + }, + ]); }); }); }); diff --git a/spec/frontend/ide/components/shared/tokened_input_spec.js b/spec/frontend/ide/components/shared/tokened_input_spec.js index 2efef9918b1..b70c9659e46 100644 --- a/spec/frontend/ide/components/shared/tokened_input_spec.js +++ b/spec/frontend/ide/components/shared/tokened_input_spec.js @@ -1,5 +1,4 @@ -import Vue, { nextTick } from 'vue'; -import mountComponent from 'helpers/vue_mount_component_helper'; +import { mount } from '@vue/test-utils'; import TokenedInput from '~/ide/components/shared/tokened_input.vue'; const TEST_PLACEHOLDER = 'Searching in test'; @@ -10,120 +9,106 @@ const TEST_TOKENS = [ ]; const TEST_VALUE = 'lorem'; -function getTokenElements(vm) { - return Array.from(vm.$el.querySelectorAll('.filtered-search-token button')); -} - -function createBackspaceEvent() { - const e = new Event('keyup'); - e.keyCode = 8; - e.which = e.keyCode; - e.altKey = false; - e.ctrlKey = true; - e.shiftKey = false; - e.metaKey = false; - return e; +function getTokenElements(wrapper) { + return wrapper.findAll('.filtered-search-token button'); } describe('IDE shared/TokenedInput', () => { - const Component = Vue.extend(TokenedInput); - let vm; - - beforeEach(() => { - vm = mountComponent(Component, { - tokens: TEST_TOKENS, - placeholder: TEST_PLACEHOLDER, - value: TEST_VALUE, + let wrapper; + + const createComponent = (props = {}) => { + wrapper = mount(TokenedInput, { + propsData: { + tokens: TEST_TOKENS, + placeholder: TEST_PLACEHOLDER, + value: TEST_VALUE, + ...props, + }, + attachTo: document.body, }); - - jest.spyOn(vm, '$emit').mockImplementation(() => {}); - }); + }; afterEach(() => { - vm.$destroy(); + wrapper.destroy(); }); it('renders tokens', () => { - const renderedTokens = getTokenElements(vm).map((x) => x.textContent.trim()); + createComponent(); + const renderedTokens = getTokenElements(wrapper).wrappers.map((w) => w.text()); expect(renderedTokens).toEqual(TEST_TOKENS.map((x) => x.label)); }); it('renders input', () => { - expect(vm.$refs.input).toBeInstanceOf(HTMLInputElement); - expect(vm.$refs.input).toHaveValue(TEST_VALUE); - }); - - it('renders placeholder, when tokens are empty', async () => { - vm.tokens = []; + createComponent(); - await nextTick(); - expect(vm.$refs.input).toHaveAttr('placeholder', TEST_PLACEHOLDER); + expect(wrapper.find('input').element).toBeInstanceOf(HTMLInputElement); + expect(wrapper.find('input').element).toHaveValue(TEST_VALUE); }); - it('triggers "removeToken" on token click', () => { - getTokenElements(vm)[0].click(); + it('renders placeholder, when tokens are empty', () => { + createComponent({ tokens: [] }); - expect(vm.$emit).toHaveBeenCalledWith('removeToken', TEST_TOKENS[0]); + expect(wrapper.find('input').attributes('placeholder')).toBe(TEST_PLACEHOLDER); }); - it('when input triggers backspace event, it calls "onBackspace"', () => { - jest.spyOn(vm, 'onBackspace').mockImplementation(() => {}); + it('triggers "removeToken" on token click', async () => { + createComponent(); + await getTokenElements(wrapper).at(0).trigger('click'); - vm.$refs.input.dispatchEvent(createBackspaceEvent()); - vm.$refs.input.dispatchEvent(createBackspaceEvent()); - - expect(vm.onBackspace).toHaveBeenCalledTimes(2); + expect(wrapper.emitted('removeToken')[0]).toStrictEqual([TEST_TOKENS[0]]); }); - it('triggers "removeToken" on backspaces when value is empty', () => { - vm.value = ''; - - vm.onBackspace(); + it('removes token on backspace when value is empty', async () => { + createComponent({ value: '' }); - expect(vm.$emit).not.toHaveBeenCalled(); - expect(vm.backspaceCount).toEqual(1); + expect(wrapper.emitted('removeToken')).toBeUndefined(); - vm.onBackspace(); + await wrapper.find('input').trigger('keyup.delete'); + await wrapper.find('input').trigger('keyup.delete'); - expect(vm.$emit).toHaveBeenCalledWith('removeToken', TEST_TOKENS[TEST_TOKENS.length - 1]); - expect(vm.backspaceCount).toEqual(0); + expect(wrapper.emitted('removeToken')[0]).toStrictEqual([TEST_TOKENS[TEST_TOKENS.length - 1]]); }); - it('does not trigger "removeToken" on backspaces when value is not empty', () => { - vm.onBackspace(); - vm.onBackspace(); + it('does not trigger "removeToken" on backspaces when value is not empty', async () => { + createComponent({ value: 'SOMETHING' }); + + await wrapper.find('input').trigger('keyup.delete'); + await wrapper.find('input').trigger('keyup.delete'); - expect(vm.backspaceCount).toEqual(0); - expect(vm.$emit).not.toHaveBeenCalled(); + expect(wrapper.emitted('removeToken')).toBeUndefined(); }); - it('does not trigger "removeToken" on backspaces when tokens are empty', () => { - vm.tokens = []; + it('does not trigger "removeToken" on backspaces when tokens are empty', async () => { + createComponent({ value: '', tokens: [] }); - vm.onBackspace(); - vm.onBackspace(); + await wrapper.find('input').trigger('keyup.delete'); + await wrapper.find('input').trigger('keyup.delete'); - expect(vm.backspaceCount).toEqual(0); - expect(vm.$emit).not.toHaveBeenCalled(); + expect(wrapper.emitted('removeToken')).toBeUndefined(); }); - it('triggers "focus" on input focus', () => { - vm.$refs.input.dispatchEvent(new Event('focus')); + it('triggers "focus" on input focus', async () => { + createComponent(); - expect(vm.$emit).toHaveBeenCalledWith('focus'); + await wrapper.find('input').trigger('focus'); + + expect(wrapper.emitted('focus')).toHaveLength(1); }); - it('triggers "blur" on input blur', () => { - vm.$refs.input.dispatchEvent(new Event('blur')); + it('triggers "blur" on input blur', async () => { + createComponent(); + + await wrapper.find('input').trigger('blur'); - expect(vm.$emit).toHaveBeenCalledWith('blur'); + expect(wrapper.emitted('blur')).toHaveLength(1); }); - it('triggers "input" with value on input change', () => { - vm.$refs.input.value = 'something-else'; - vm.$refs.input.dispatchEvent(new Event('input')); + it('triggers "input" with value on input change', async () => { + createComponent(); + + await wrapper.find('input').setValue('something-else'); - expect(vm.$emit).toHaveBeenCalledWith('input', 'something-else'); + expect(wrapper.emitted('input')[0]).toStrictEqual(['something-else']); }); }); -- cgit v1.2.3