diff options
Diffstat (limited to 'spec/frontend/ide/components')
9 files changed, 211 insertions, 201 deletions
diff --git a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js b/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js deleted file mode 100644 index 42e0a20bc7b..00000000000 --- a/spec/frontend/ide/components/commit_sidebar/list_collapsed_spec.js +++ /dev/null @@ -1,75 +0,0 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; -import { createStore } from '~/ide/stores'; -import listCollapsed from '~/ide/components/commit_sidebar/list_collapsed.vue'; -import { file } from '../../helpers'; -import { removeWhitespace } from '../../../helpers/text_helper'; - -describe('Multi-file editor commit sidebar list collapsed', () => { - let vm; - let store; - - beforeEach(() => { - store = createStore(); - - const Component = Vue.extend(listCollapsed); - - vm = createComponentWithStore(Component, store, { - files: [ - { - ...file('file1'), - tempFile: true, - }, - file('file2'), - ], - iconName: 'staged', - title: 'Staged', - }); - - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); - }); - - it('renders added & modified files count', () => { - expect(removeWhitespace(vm.$el.textContent).trim()).toBe('1 1'); - }); - - describe('addedFilesLength', () => { - it('returns an length of temp files', () => { - expect(vm.addedFilesLength).toBe(1); - }); - }); - - describe('modifiedFilesLength', () => { - it('returns an length of modified files', () => { - expect(vm.modifiedFilesLength).toBe(1); - }); - }); - - describe('addedFilesIconClass', () => { - it('includes multi-file-addition when addedFiles is not empty', () => { - expect(vm.addedFilesIconClass).toContain('multi-file-addition'); - }); - - it('excludes multi-file-addition when addedFiles is empty', () => { - vm.files = []; - - expect(vm.addedFilesIconClass).not.toContain('multi-file-addition'); - }); - }); - - describe('modifiedFilesClass', () => { - it('includes multi-file-modified when addedFiles is not empty', () => { - expect(vm.modifiedFilesClass).toContain('multi-file-modified'); - }); - - it('excludes multi-file-modified when addedFiles is empty', () => { - vm.files = []; - - expect(vm.modifiedFilesClass).not.toContain('multi-file-modified'); - }); - }); -}); diff --git a/spec/frontend/ide/components/commit_sidebar/list_spec.js b/spec/frontend/ide/components/commit_sidebar/list_spec.js index 2107ff96e95..636dfbf0b2a 100644 --- a/spec/frontend/ide/components/commit_sidebar/list_spec.js +++ b/spec/frontend/ide/components/commit_sidebar/list_spec.js @@ -16,7 +16,6 @@ describe('Multi-file editor commit sidebar list', () => { vm = createComponentWithStore(Component, store, { title: 'Staged', fileList: [], - iconName: 'staged', action: 'stageAllChanges', actionBtnText: 'stage all', actionBtnIcon: 'history', diff --git a/spec/frontend/ide/components/ide_side_bar_spec.js b/spec/frontend/ide/components/ide_side_bar_spec.js index 86e4e8d8f89..72e9463945b 100644 --- a/spec/frontend/ide/components/ide_side_bar_spec.js +++ b/spec/frontend/ide/components/ide_side_bar_spec.js @@ -1,10 +1,12 @@ import { mount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; import { GlSkeletonLoading } from '@gitlab/ui'; +import waitForPromises from 'helpers/wait_for_promises'; import { createStore } from '~/ide/stores'; import IdeSidebar from '~/ide/components/ide_side_bar.vue'; import IdeTree from '~/ide/components/ide_tree.vue'; import RepoCommitSection from '~/ide/components/repo_commit_section.vue'; +import IdeReview from '~/ide/components/ide_review.vue'; import { leftSidebarViews } from '~/ide/constants'; import { projectData } from '../mock_data'; @@ -15,11 +17,12 @@ describe('IdeSidebar', () => { let wrapper; let store; - function createComponent() { + function createComponent({ view = leftSidebarViews.edit.name } = {}) { store = createStore(); store.state.currentProjectId = 'abcproject'; store.state.projects.abcproject = projectData; + store.state.currentActivityView = view; return mount(IdeSidebar, { store, @@ -48,22 +51,46 @@ describe('IdeSidebar', () => { expect(wrapper.findAll(GlSkeletonLoading)).toHaveLength(3); }); - describe('activityBarComponent', () => { - it('renders tree component', () => { + describe('deferred rendering components', () => { + it('fetches components on demand', async () => { wrapper = createComponent(); expect(wrapper.find(IdeTree).exists()).toBe(true); - }); + expect(wrapper.find(IdeReview).exists()).toBe(false); + expect(wrapper.find(RepoCommitSection).exists()).toBe(false); - it('renders commit component', async () => { - wrapper = createComponent(); + store.state.currentActivityView = leftSidebarViews.review.name; + await waitForPromises(); + await wrapper.vm.$nextTick(); - store.state.currentActivityView = leftSidebarViews.commit.name; + expect(wrapper.find(IdeTree).exists()).toBe(false); + expect(wrapper.find(IdeReview).exists()).toBe(true); + expect(wrapper.find(RepoCommitSection).exists()).toBe(false); + store.state.currentActivityView = leftSidebarViews.commit.name; + await waitForPromises(); await wrapper.vm.$nextTick(); + expect(wrapper.find(IdeTree).exists()).toBe(false); + expect(wrapper.find(IdeReview).exists()).toBe(false); expect(wrapper.find(RepoCommitSection).exists()).toBe(true); }); + it.each` + view | tree | review | commit + ${leftSidebarViews.edit.name} | ${true} | ${false} | ${false} + ${leftSidebarViews.review.name} | ${false} | ${true} | ${false} + ${leftSidebarViews.commit.name} | ${false} | ${false} | ${true} + `('renders correct panels for $view', async ({ view, tree, review, commit } = {}) => { + wrapper = createComponent({ + view, + }); + await waitForPromises(); + await wrapper.vm.$nextTick(); + + expect(wrapper.find(IdeTree).exists()).toBe(tree); + expect(wrapper.find(IdeReview).exists()).toBe(review); + expect(wrapper.find(RepoCommitSection).exists()).toBe(commit); + }); }); it('keeps the current activity view components alive', async () => { @@ -72,7 +99,7 @@ describe('IdeSidebar', () => { const ideTreeComponent = wrapper.find(IdeTree).element; store.state.currentActivityView = leftSidebarViews.commit.name; - + await waitForPromises(); await wrapper.vm.$nextTick(); expect(wrapper.find(IdeTree).exists()).toBe(false); @@ -80,6 +107,7 @@ describe('IdeSidebar', () => { store.state.currentActivityView = leftSidebarViews.edit.name; + await waitForPromises(); await wrapper.vm.$nextTick(); // reference to the elements remains the same, meaning the components were kept alive diff --git a/spec/frontend/ide/components/ide_spec.js b/spec/frontend/ide/components/ide_spec.js index a7b07a9f0e2..ff3852b6775 100644 --- a/spec/frontend/ide/components/ide_spec.js +++ b/spec/frontend/ide/components/ide_spec.js @@ -1,127 +1,165 @@ -import Vue from 'vue'; -import { createComponentWithStore } from 'helpers/vue_mount_component_helper'; +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlButton, GlLoadingIcon } from '@gitlab/ui'; import { createStore } from '~/ide/stores'; +import ErrorMessage from '~/ide/components/error_message.vue'; +import FindFile from '~/vue_shared/components/file_finder/index.vue'; +import CommitEditorHeader from '~/ide/components/commit_sidebar/editor_header.vue'; +import RepoTabs from '~/ide/components/repo_tabs.vue'; +import IdeStatusBar from '~/ide/components/ide_status_bar.vue'; +import RightPane from '~/ide/components/panes/right.vue'; +import NewModal from '~/ide/components/new_dropdown/modal.vue'; + import ide from '~/ide/components/ide.vue'; import { file } from '../helpers'; import { projectData } from '../mock_data'; -import extendStore from '~/ide/stores/extend'; - -let store; -function bootstrap(projData) { - store = createStore(); +const localVue = createLocalVue(); +localVue.use(Vuex); - extendStore(store, document.createElement('div')); +describe('WebIDE', () => { + const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; - const Component = Vue.extend(ide); + let wrapper; - store.state.currentProjectId = 'abcproject'; - store.state.currentBranchId = 'master'; - store.state.projects.abcproject = { ...projData }; - Vue.set(store.state.trees, 'abcproject/master', { - tree: [], - loading: false, - }); - - return createComponentWithStore(Component, store, { - emptyStateSvgPath: 'svg', - noChangesStateSvgPath: 'svg', - committedStateSvgPath: 'svg', - }); -} + function createComponent({ projData = emptyProjData, state = {} } = {}) { + const store = createStore(); -describe('ide component, empty repo', () => { - let vm; + store.state.currentProjectId = 'abcproject'; + store.state.currentBranchId = 'master'; + store.state.projects.abcproject = { ...projData }; + store.state.trees['abcproject/master'] = { + tree: [], + loading: false, + }; + Object.keys(state).forEach(key => { + store.state[key] = state[key]; + }); - beforeEach(() => { - const emptyProjData = { ...projectData, empty_repo: true, branches: {} }; - vm = bootstrap(emptyProjData); - vm.$mount(); - }); + return shallowMount(ide, { + store, + localVue, + stubs: { + ErrorMessage, + GlButton, + GlLoadingIcon, + CommitEditorHeader, + RepoTabs, + IdeStatusBar, + FindFile, + RightPane, + NewModal, + }, + }); + } afterEach(() => { - vm.$destroy(); + wrapper.destroy(); + wrapper = null; }); - it('renders "New file" button in empty repo', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).not.toBeNull(); - done(); + describe('ide component, empty repo', () => { + beforeEach(() => { + wrapper = createComponent({ + projData: { + empty_repo: true, + }, + }); }); - }); -}); - -describe('ide component, non-empty repo', () => { - let vm; - beforeEach(() => { - vm = bootstrap(projectData); - vm.$mount(); - }); - - afterEach(() => { - vm.$destroy(); + it('renders "New file" button in empty repo', async () => { + expect(wrapper.find('[title="New file"]').exists()).toBe(true); + }); }); - it('shows error message when set', done => { - expect(vm.$el.querySelector('.gl-alert')).toBe(null); + describe('ide component, non-empty repo', () => { + describe('error message', () => { + it('does not show error message when it is not set', () => { + wrapper = createComponent({ + state: { + errorMessage: null, + }, + }); - vm.$store.state.errorMessage = { - text: 'error', - }; + expect(wrapper.find(ErrorMessage).exists()).toBe(false); + }); - vm.$nextTick(() => { - expect(vm.$el.querySelector('.gl-alert')).not.toBe(null); + it('shows error message when set', () => { + wrapper = createComponent({ + state: { + errorMessage: { + text: 'error', + }, + }, + }); - done(); + expect(wrapper.find(ErrorMessage).exists()).toBe(true); + }); }); - }); - describe('onBeforeUnload', () => { - it('returns undefined when no staged files or changed files', () => { - expect(vm.onBeforeUnload()).toBe(undefined); - }); + describe('onBeforeUnload', () => { + it('returns undefined when no staged files or changed files', () => { + wrapper = createComponent(); + expect(wrapper.vm.onBeforeUnload()).toBe(undefined); + }); - it('returns warning text when their are changed files', () => { - vm.$store.state.changedFiles.push(file()); + it('returns warning text when their are changed files', () => { + wrapper = createComponent({ + state: { + changedFiles: [file()], + }, + }); - expect(vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); - }); + expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + }); - it('returns warning text when their are staged files', () => { - vm.$store.state.stagedFiles.push(file()); + it('returns warning text when their are staged files', () => { + wrapper = createComponent({ + state: { + stagedFiles: [file()], + }, + }); - expect(vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); - }); + expect(wrapper.vm.onBeforeUnload()).toBe('Are you sure you want to lose unsaved changes?'); + }); - it('updates event object', () => { - const event = {}; - vm.$store.state.stagedFiles.push(file()); + it('updates event object', () => { + const event = {}; + wrapper = createComponent({ + state: { + stagedFiles: [file()], + }, + }); - vm.onBeforeUnload(event); + wrapper.vm.onBeforeUnload(event); - expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?'); + expect(event.returnValue).toBe('Are you sure you want to lose unsaved changes?'); + }); }); - }); - describe('non-existent branch', () => { - it('does not render "New file" button for non-existent branch when repo is not empty', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); - done(); + describe('non-existent branch', () => { + it('does not render "New file" button for non-existent branch when repo is not empty', () => { + wrapper = createComponent({ + state: { + projects: {}, + }, + }); + + expect(wrapper.find('[title="New file"]').exists()).toBe(false); }); }); - }); - describe('branch with files', () => { - beforeEach(() => { - store.state.trees['abcproject/master'].tree = [file()]; - }); + describe('branch with files', () => { + beforeEach(() => { + wrapper = createComponent({ + projData: { + empty_repo: false, + }, + }); + }); - it('does not render "New file" button', done => { - vm.$nextTick(() => { - expect(vm.$el.querySelector('.ide-empty-state button[title="New file"]')).toBeNull(); - done(); + it('does not render "New file" button', () => { + expect(wrapper.find('[title="New file"]').exists()).toBe(false); }); }); }); diff --git a/spec/frontend/ide/components/ide_status_list_spec.js b/spec/frontend/ide/components/ide_status_list_spec.js index bb8165d1a52..02b5dc19bd8 100644 --- a/spec/frontend/ide/components/ide_status_list_spec.js +++ b/spec/frontend/ide/components/ide_status_list_spec.js @@ -6,17 +6,21 @@ import TerminalSyncStatusSafe from '~/ide/components/terminal_sync/terminal_sync const TEST_FILE = { name: 'lorem.md', - editorRow: 3, - editorColumn: 23, - fileLanguage: 'markdown', content: 'abc\nndef', permalink: '/lorem.md', }; +const TEST_FILE_EDITOR = { + fileLanguage: 'markdown', + editorRow: 3, + editorColumn: 23, +}; +const TEST_EDITOR_POSITION = `${TEST_FILE_EDITOR.editorRow}:${TEST_FILE_EDITOR.editorColumn}`; const localVue = createLocalVue(); localVue.use(Vuex); describe('ide/components/ide_status_list', () => { + let activeFileEditor; let activeFile; let store; let wrapper; @@ -27,6 +31,14 @@ describe('ide/components/ide_status_list', () => { getters: { activeFile: () => activeFile, }, + modules: { + editor: { + namespaced: true, + getters: { + activeFileEditor: () => activeFileEditor, + }, + }, + }, }); wrapper = shallowMount(IdeStatusList, { @@ -38,6 +50,7 @@ describe('ide/components/ide_status_list', () => { beforeEach(() => { activeFile = TEST_FILE; + activeFileEditor = TEST_FILE_EDITOR; }); afterEach(() => { @@ -47,8 +60,6 @@ describe('ide/components/ide_status_list', () => { wrapper = null; }); - const getEditorPosition = file => `${file.editorRow}:${file.editorColumn}`; - describe('with regular file', () => { beforeEach(() => { createComponent(); @@ -65,11 +76,11 @@ describe('ide/components/ide_status_list', () => { }); it('shows file editor position', () => { - expect(wrapper.text()).toContain(getEditorPosition(TEST_FILE)); + expect(wrapper.text()).toContain(TEST_EDITOR_POSITION); }); it('shows file language', () => { - expect(wrapper.text()).toContain(TEST_FILE.fileLanguage); + expect(wrapper.text()).toContain(TEST_FILE_EDITOR.fileLanguage); }); }); @@ -81,7 +92,7 @@ describe('ide/components/ide_status_list', () => { }); it('does not show file editor position', () => { - expect(wrapper.text()).not.toContain(getEditorPosition(TEST_FILE)); + expect(wrapper.text()).not.toContain(TEST_EDITOR_POSITION); }); }); diff --git a/spec/frontend/ide/components/pipelines/list_spec.js b/spec/frontend/ide/components/pipelines/list_spec.js index 7f083fa7c25..c1744fefe20 100644 --- a/spec/frontend/ide/components/pipelines/list_spec.js +++ b/spec/frontend/ide/components/pipelines/list_spec.js @@ -1,11 +1,10 @@ import { shallowMount, createLocalVue } from '@vue/test-utils'; import Vuex from 'vuex'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlTab } from '@gitlab/ui'; import { TEST_HOST } from 'helpers/test_constants'; import { pipelines } from 'jest/ide/mock_data'; import List from '~/ide/components/pipelines/list.vue'; import JobsList from '~/ide/components/jobs/list.vue'; -import Tab from '~/vue_shared/components/tabs/tab.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import IDEServices from '~/ide/services'; @@ -167,7 +166,7 @@ describe('IDE pipelines list', () => { createComponent({}, { ...withLatestPipelineState, stages, isLoadingJobs }); const jobProps = wrapper - .findAll(Tab) + .findAll(GlTab) .at(0) .find(JobsList) .props(); @@ -182,7 +181,7 @@ describe('IDE pipelines list', () => { createComponent({}, { ...withLatestPipelineState, isLoadingJobs }); const jobProps = wrapper - .findAll(Tab) + .findAll(GlTab) .at(1) .find(JobsList) .props(); diff --git a/spec/frontend/ide/components/repo_editor_spec.js b/spec/frontend/ide/components/repo_editor_spec.js index 9f4c9c1622a..71a4f08cfb4 100644 --- a/spec/frontend/ide/components/repo_editor_spec.js +++ b/spec/frontend/ide/components/repo_editor_spec.js @@ -55,7 +55,6 @@ describe('RepoEditor', () => { beforeEach(() => { const f = { ...file('file.txt'), - viewMode: FILE_VIEW_MODE_EDITOR, content: 'hello world', }; @@ -92,6 +91,8 @@ describe('RepoEditor', () => { }); const findEditor = () => vm.$el.querySelector('.multi-file-editor-holder'); + const changeViewMode = viewMode => + store.dispatch('editor/updateFileEditor', { path: vm.file.path, data: { viewMode } }); describe('default', () => { beforeEach(() => { @@ -409,7 +410,7 @@ describe('RepoEditor', () => { describe('when files view mode is preview', () => { beforeEach(done => { jest.spyOn(vm.editor, 'updateDimensions').mockImplementation(); - vm.file.viewMode = FILE_VIEW_MODE_PREVIEW; + changeViewMode(FILE_VIEW_MODE_PREVIEW); vm.file.name = 'myfile.md'; vm.file.content = 'hello world'; @@ -423,7 +424,7 @@ describe('RepoEditor', () => { describe('when file view mode changes to editor', () => { it('should update dimensions', () => { - vm.file.viewMode = FILE_VIEW_MODE_EDITOR; + changeViewMode(FILE_VIEW_MODE_EDITOR); return vm.$nextTick().then(() => { expect(vm.editor.updateDimensions).toHaveBeenCalled(); diff --git a/spec/frontend/ide/components/repo_tab_spec.js b/spec/frontend/ide/components/repo_tab_spec.js index f35726de27c..a44c8b4d5ee 100644 --- a/spec/frontend/ide/components/repo_tab_spec.js +++ b/spec/frontend/ide/components/repo_tab_spec.js @@ -100,6 +100,18 @@ describe('RepoTab', () => { expect(wrapper.find('.file-modified').exists()).toBe(true); }); + it.each` + tabProps | closeLabel + ${{}} | ${'Close foo.txt'} + ${{ changed: true }} | ${'foo.txt changed'} + `('close button has label ($closeLabel) with tab ($tabProps)', ({ tabProps, closeLabel }) => { + const tab = { ...file('foo.txt'), ...tabProps }; + + createComponent({ tab }); + + expect(wrapper.find('button').attributes('aria-label')).toBe(closeLabel); + }); + describe('locked file', () => { let f; @@ -122,9 +134,7 @@ describe('RepoTab', () => { }); it('renders a tooltip', () => { - expect(wrapper.find('span:nth-child(2)').attributes('data-original-title')).toContain( - 'Locked by testuser', - ); + expect(wrapper.find('span:nth-child(2)').attributes('title')).toBe('Locked by testuser'); }); }); diff --git a/spec/frontend/ide/components/terminal/empty_state_spec.js b/spec/frontend/ide/components/terminal/empty_state_spec.js index a3f2089608d..b62470f67b6 100644 --- a/spec/frontend/ide/components/terminal/empty_state_spec.js +++ b/spec/frontend/ide/components/terminal/empty_state_spec.js @@ -1,5 +1,5 @@ import { shallowMount } from '@vue/test-utils'; -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlLoadingIcon, GlButton, GlAlert } from '@gitlab/ui'; import { TEST_HOST } from 'spec/test_constants'; import TerminalEmptyState from '~/ide/components/terminal/empty_state.vue'; @@ -36,7 +36,7 @@ describe('IDE TerminalEmptyState', () => { const img = wrapper.find('.svg-content img'); expect(img.exists()).toBe(true); - expect(img.attributes('src')).toEqual(TEST_PATH); + expect(img.attributes('src')).toBe(TEST_PATH); }); it('when loading, shows loading icon', () => { @@ -71,24 +71,23 @@ describe('IDE TerminalEmptyState', () => { }, }); - button = wrapper.find('button'); + button = wrapper.find(GlButton); }); it('shows button', () => { - expect(button.text()).toEqual('Start Web Terminal'); - expect(button.attributes('disabled')).toBeFalsy(); + expect(button.text()).toBe('Start Web Terminal'); + expect(button.props('disabled')).toBe(false); }); it('emits start when button is clicked', () => { - expect(wrapper.emitted().start).toBeFalsy(); - - button.trigger('click'); + expect(wrapper.emitted().start).toBeUndefined(); + button.vm.$emit('click'); expect(wrapper.emitted().start).toHaveLength(1); }); it('shows help path link', () => { - expect(wrapper.find('a').attributes('href')).toEqual(TEST_HELP_PATH); + expect(wrapper.find('a').attributes('href')).toBe(TEST_HELP_PATH); }); }); @@ -101,7 +100,7 @@ describe('IDE TerminalEmptyState', () => { }, }); - expect(wrapper.find('button').attributes('disabled')).not.toBe(null); - expect(wrapper.find('.bs-callout').element.innerHTML).toEqual(TEST_HTML_MESSAGE); + expect(wrapper.find(GlButton).props('disabled')).toBe(true); + expect(wrapper.find(GlAlert).html()).toContain(TEST_HTML_MESSAGE); }); }); |